How to deploy XWiki on AWS with One Click

Thumbnail

Deploy your selfhosted serverless XWiki now!

There are many ways to get your own, truly private and selfhosted, wiki in the exciting cloud computing times. SaaS solutions exists and many OpenSource Software can be hosted on virtual machines or even hardware.

But what is the best way to deploy such Wiki on your AWS Account? This blog post shows you how to operate a deployment of XWiki without harming any servers. At least none you are managing, so the described one click deployment does require very less operational effort.

Architecture for XWiki - Without Harming Servers!

Throughout this blog post we will discuss the following architecture and components of the serverless XWiki deployment. As always, we have three layers which a crucial for this deployment:

  1. Ingress/Connection Layer: Consisting of managed AWS DNS with AWS Route53 and managed AWS LoadBalancing with AWS ElasticLoadbalancing
  2. Compute Layer: Consisting of managed containers running on AWS ECS Fargate, a managed container run engine that manages all needed infrastructure
  3. Storage/Database Layer: Consisting of managed serverless MySQL DB powered by AWS Aurora Serverless for MySQL and managed NFS filesystem powered by AWS ElasticFilesystem

XWiki-Architecture

Fully automated One-Click Deployment

To make the deployment as easy as possible we created a fully integrated infrastructure as code solution using the AWS Cloud Development Kit (AWS CDK). This solution consists of all necessary components shown in the architecture and connecting them to have a working one click deployment. But let’s take a look at the configuration parts within the AWS CDK in more detail.

Base Network Configuration

We create a small VPC network for our XWiki Deployment using the configuration below which consists of:

  • AWS managed NAT Gateways
  • Subnet Configuration:

    • Two public subnets (CIDR /27) in two different Availability Zones
    • Two private subnets (CIDR /26) in two different Availability Zones

      const xwikiVpc = new Vpc(this, 'trcVpc', {
      cidr: '10.42.42.0/24',
      defaultInstanceTenancy: DefaultInstanceTenancy.DEFAULT,
      maxAzs: 2,
      natGatewayProvider: NatProvider.gateway(),
      natGateways: 1,
      subnetConfiguration: [
      {
      name: 'public',
      subnetType: SubnetType.PUBLIC,
      cidrMask: 27
      },
      {
      name: 'private-database',
      subnetType: SubnetType.PRIVATE,
      cidrMask: 26
      }
      ]
      });
      

Note: We are using a very small network CIDR portion /24 (with four subnets; two public and two private) this is sufficient for this deployment but is not recommended when you want to deploy other services within this network in the future as well.

Our Job Zero - Creating Encryption Keys

With everything we do on AWS and the Public Cloud in general security and encryption should be our job zero so we are creating two AWS KMS Encryption Keys. One to be used for storage at rest encryption for our file system and database storage and one to be used for encrypting our secrets (e.g. DB Password)

const xwikiEncryptionKey = new Key(this, 'trcXWikiEncryptionKey', {
  alias: `trc-xwiki`,
  description: `Encryption Key for XWiki Storage Resources`,
  enableKeyRotation: true,
  enabled: true,
  trustAccountIdentities: true,
});

const xwikiSecretEncryptionKey = new Key(this, 'trcXWikiSecretEncryptionKey', {
  alias: `trc-xwiki-secret`,
  description: `Encryption Key for XWiki Secrets`,
  enableKeyRotation: true,
  enabled: true,
  trustAccountIdentities: true,
});

Creating our File Storage and Databases

The first important part when creating a serverless infrastructure to host a persistent wiki solution as XWiki is where to store files that need to be persistently available. Therefore as shown in the architecture diagram we are creating a managed file system using AWS EFS. Highlights of the AWS EFS configuration:

  • Automatic Backups are enabled
  • Storage is encrypted with our previously configured Encryption Key (AWS KMS CMK)
  • File system is located in the private subnet portion of our VPC

    const xwikiEfsSg = new SecurityGroup(this, 'trcXWikiEfsSecurityGroup', {
    vpc: xwikiVpc,
    allowAllOutbound: true,
    description: `Security Group for XWiki EFS`
    });
    
    const xwikiEfs = new FileSystem(this, 'trcXWikiFileSystem', {
    vpc: xwikiVpc,
    enableAutomaticBackups: true,
    encrypted: true,
    kmsKey: xwikiEncryptionKey,
    performanceMode: PerformanceMode.GENERAL_PURPOSE,
    securityGroup: xwikiEfsSg,
    vpcSubnets: xwikiVpc.selectSubnets(
    {
      subnetType: SubnetType.PRIVATE
    }
    )
    });
    

Creation of an AWS RDS Aurora serverless DB is the next step within our solution. This will create a MySQL DB that is fully managed by AWS from the infrastructure point of view, so now DB Servers to manage for us here. Highlights of the RDS Aurora configuration:

  • Dynamically generated DB Master PW which is stored within AWS SecretsManager
  • Scaling Configuration for our Aurora Capacity Units (vertical scaling based on the load on the database)
  • Encrypted Storage using our previously configured Encryption Key
  • Database is located in the private subnet portion of our VPC

    const xwikiRdsSg = new SecurityGroup(this, 'trcXWikiRdsSecurityGroup', {
    vpc: xwikiVpc,
    allowAllOutbound: true,
    description: `Security Group for XWiki RDS`
    });
    
    const xwikiRdsDbSubnetGroup = new SubnetGroup(this, 'trcXWikiDbSubnetGroup', {
    description: `DB SubnetGroup for XWiki RDS`,
    vpc: xwikiVpc,
    vpcSubnets: xwikiVpc.selectSubnets(
    {
      subnetType: SubnetType.PRIVATE
    }
    )
    });
    
    const xwikiRdsPwSecret = new Secret(this, 'trcXWikiEcsUserPassword', {
    description: `RDS UserSecret for XWiki RDS`,
    encryptionKey: xwikiSecretEncryptionKey,
    generateSecretString: {
    excludePunctuation: true,
    passwordLength: 16
    }
    });
    
    const xwikiRds = new ServerlessCluster(this, 'trcXWikiDbCluster', {
    engine: DatabaseClusterEngine.auroraPostgres({
    version: AuroraPostgresEngineVersion.VER_10_7
    }),
    vpc: xwikiVpc,
    vpcSubnets: xwikiVpc.selectSubnets(
    {
      subnetType: SubnetType.PRIVATE
    }
    ),
    credentials: {
    username: 'xwikimysql',
    password: xwikiRdsPwSecret.secretValue,
    },
    backupRetention: cdk.Duration.days(7),
    scaling: {
    autoPause: cdk.Duration.minutes(0), // AutoPause Disabled
    minCapacity: AuroraCapacityUnit.ACU_2,
    maxCapacity: AuroraCapacityUnit.ACU_8
    },
    securityGroups: [
    xwikiRdsSg
    ],
    defaultDatabaseName: 'xwiki',
    storageEncryptionKey: xwikiEncryptionKey,
    subnetGroup: xwikiRdsDbSubnetGroup
    });
    

Note: The configured Aurora MySQL Engine Version is suitable for AWS RDS Aurora Serverless deployments within the Frankfurt (eu-central-1) region, keep in mind that this configuration option needs to be adjusted for other regions and might not be available everywhere.

Creating our ECS Fargate Service

Before we can actually create our AWS ECS Fargate Service some prerequisite Resources need to be created. We start off with the creation of our AWS ECS Cluster and activate Container Insights on it.

const xwikiFargateCluster = new Cluster(this, 'trcXWikiCluster', {
  containerInsights: true,
  vpc: xwikiVpc
});

The next step is the creation of our actual Task definition where we basically configure the following properties:

  • vCPU resources available to our XWIKI container, aligned to the documentation of XWIKI we are going to use two vCPUs here
  • RAM resources available to our XWIKI container, using the minimum configuration for ECS Fargate Tasks with two vCPUs as this is even more than stated in the XWIKI documentation
  • Volumes available to our XWIKI container, basically this is the connection of our AWS EFS file system to our XWIKI container

    const xwikiTaskIamRole = new Role(this, 'trcXwikiTaskRole', {
    assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'),
    description: `IAM Task Role for XWiki ECS Fargate`,
    });
    
    const xwikiTaskDefinition = new FargateTaskDefinition(this, 'trcXWikiTaskDefinition', {
    cpu: 2048,
    memoryLimitMiB: 4096,
    volumes: [
    {
      name: 'EfsPersistendVolume',
      efsVolumeConfiguration: {
        fileSystemId: xwikiEfs.fileSystemId,
        rootDirectory: '/',
        transitEncryption: 'ENABLED'
      }
    }
    ],
    executionRole: xwikiTaskIamRole,
    taskRole: xwikiTaskIamRole
    });
    

Note: For the taskdefinition to be created we first create a IAM Role Container to handle execution permissions; Access to AWS CloudWatch Logs and AWS ECR.

The last step of the creation of the AWS ECS Taskdefinition is to define and create the corresponding AWS CloudWatch LogGroup and grant permissions to the before created IAM Role container. Further we configure the XWiki Docker container within the task definition with the following properties:

  • Needed Environment variables for the connection to the Database (these are published in the XWiki container documentation)
  • Logging configuration to store all container logs into the created AWS CloudWatch LogGroup
  • Adding the XWiki Docker image directly from Docker Hub (using XWIKI version 12.8 with MySQL configuration)
  • Mounting the EFS file system into the container with the specified path and read-write permissions, to host all persistently created files by XWiki

    const xwikiLogGroup = new LogGroup(this, 'trcXWikiLogGroup', {
    retention: RetentionDays.ONE_MONTH
    });
    
    xwikiLogGroup.grantWrite(xwikiTaskIamRole);
    
    const xwikiContainer = xwikiTaskDefinition.addContainer('XWikiImage', {
    image: ContainerImage.fromRegistry('xwiki:12.8-mysql-tomcat'),
    environment: {
    'DB_HOST': xwikiRds.clusterEndpoint.hostname,
    'DB_DATABASE': 'xwiki',
    'DB_USER': 'xwikimysql'
    },
    logging: new AwsLogDriver({
    logGroup: xwikiLogGroup,
    streamPrefix: `trc-xwiki`
    }),
    secrets: {
    'DB_PASSWORD': EcsSecret.fromSecretsManager(xwikiRdsPwSecret)
    },
    essential: true,
    });
    
    xwikiContainer.addPortMappings({
    containerPort: 8080
    });
    
    xwikiContainer.addMountPoints({
    containerPath: '/usr/local/xwiki/data',
    readOnly: false,
    sourceVolume: 'EfsPersistendVolume'
    });
    
    const xwikiServiceSecurityGroup = new SecurityGroup(this, 'trcXWikiTaskSecurityGroup', {
    vpc: xwikiVpc,
    allowAllOutbound: true,
    description: `SecurityGroup for XWiki ECS Fargate Service`
    });
    

Note: We also add the PortMapping Configuration for the standard Tomcat Port 8080 to our container to be exposed by AWS ECS Fargate to the ingress layer.

Leaving us finally with the creation of the AWS ECS Fargate Service which will then be deployed by AWS ECS to our Cluster using our previously created task definition.

const xwikiEcsService = new FargateService(this, 'trcXWikiService', {
  cluster: xwikiFargateCluster,
  taskDefinition: xwikiTaskDefinition,
  desiredCount: 1,
  platformVersion: FargatePlatformVersion.VERSION1_4,
  vpcSubnets: xwikiVpc.selectSubnets(
    {
      subnetType: SubnetType.PRIVATE
    }
  ),
  securityGroups: [
    xwikiServiceSecurityGroup
  ]
});

Creating the Ingress Layer

Lastly, we are going to create the ingress Layer configuration, which consists of our Application Load Balancer and the following configuration:

  • Configuration of the Load Balancer to be externally available to the world (internetFacing: true)
  • Adding the Load Balancer listener to listen for HTTP traffic on Port 80
  • Adding the ECS Fargate Service es Targets to the listener to make our XWiki installation available to the world

    const xwikiLoadBalancerSecurityGroup = new SecurityGroup(this, 'trcXWikiAlbSecurityGroup', {
    vpc: xwikiVpc,
    allowAllOutbound: true,
    description: `SecurityGroup for XWiki Application LoadBalancer`
    });
    
    xwikiLoadBalancerSecurityGroup.addIngressRule(
    Peer.anyIpv4(),
    Port.tcp(80),
    `Allow HTTP Connections for the World to Application LoadBalancer`
    );
    
    const xwikiLoadBalancer = new ApplicationLoadBalancer(this, 'trcXWikiLoadBalancer', {
    vpc: xwikiVpc,
    internetFacing: true,
    securityGroup: xwikiLoadBalancerSecurityGroup
    });
    
    const xwikiLoadBalancerListener = xwikiLoadBalancer.addListener('trcXWikiLoadBalancerHttpListener', {
    protocol: ApplicationProtocol.HTTP,
    });
    
    xwikiLoadBalancerListener.addTargets('trcXWikiTargets', {
    deregistrationDelay: cdk.Duration.minutes(1),
    protocol: ApplicationProtocol.HTTP,
    targets: [
    xwikiEcsService
    ],
    healthCheck: {
    healthyHttpCodes: '200,301,302'
    }
    });
    

Disclaimer

Keep in mind that we are not showing the whole configuration here but only the important parts of this one click deployment please refer to Sources for the Github repository containing the one click AWS CDK solution.

It’s important to note that we haven’t configured DNS and HTTPS (TLS) for the LoadBalancer for this Demo but it can be easily be added to have a truly production ready deployment of this XWiki One-Click Solution. The next chapter will then discuss how to deploy the solution to your AWS Account proofing that it is really easy to deploy.

Deployment Steps

Prerequisites

  • Installation of node.js on your system
  • Installation of git on your system (recommended)
  • Configuration of AWS Credential Files or Environment variables

Deploying the One-Click Solution

Finally, we are here to deploy the One-Click Solution into your AWS Account into the AWS Frankfurt region just follow these simple steps and you have a running XWiki installation:

  1. Go to Github and Clone or Fork the sources of the solution here e.g.:

    git clone https://github.com/marcotesch/xwiki-oneclick-deployment
    
  2. Navigate into the cloned repository:

    cd xwiki-oneclick-deployment
    
  3. Install all needed node packages into the solution locally:

    npm install
    
  4. Execute the actual One Click deployment using either environment variables

    npx cdk deploy "*"
    

or specifying a configured AWS Profile (e.g.: my-profile)

npx cdk deploy "*" –profile my-profile
  1. Connect to the LoadBalancer DNS shown in the output of the previous command to configure your newly hosted XWiki installation

Sources

Sources and deployment scripts can be found on GitHub.

Did you find this approach useful? If you have additional Ideas, Questions, Additions, Feedback - just reach out to me via my contact details below.