NRIネットコム Blog

NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

塩漬け、ダメ。ゼッタイ。〜Growi Compose (on EC2)をv3→v6に更新した話〜

はじめに

最近花粉症がようやく落ち着いてきたozawaです。ようやくこの地獄のスパイラルから抜け出せそうです。

Growiとは

WESEEK社が開発しているOSSなWikiツールです。
https://growi.org/ja/

GrowiはGitHub上でcomposeファイルが公開されており、Docker Composeを使って構築が可能です。
https://docs.growi.org/ja/admin-guide/getting-started/docker-compose.html

なんで塩漬けしてた?

5年くらい前に思いつきで構築したGrowiさんでしたが、あれよあれよと利用者が増えてしまい、
私が所属するチームの情報管理としてメインで使われる存在にまでなってしまいました。

そしてふと気がつくと5年くらい経ってました。時が経つのは早いですね。←

いい加減更新しよう

なのでいい加減更新します。

最新バージョンを確認するとメジャーバージョンはv6でした。 既存環境はv3です。手遅れになる前に片をつけます。

というわけで

更新します。 今回はアーキテクチャ観点での更新について記載します。 アプリの使用感が変わったとかその辺りの話は出ませんので悪しからず。

既存構成

こんな感じです。

Docker on EC2 な Growiの構成

当時、検証目的でたてたかっただけなので、Docker Compose on EC2 でサクッと構築しました。
今後に向けて冗長化とか最適化とかもしたいところですが、とりあえず塩漬けを解消するため、基本構成はこれを踏襲します。

具体的な戦略

更新に向けては下記の課題を解消する必要があります。

  1. 既存環境のコンテナ内にあるデータ移行
  2. MongoDBバージョンアップ
  3. ホストOSバージョンアップ

なのでこの辺を解消していく形で対応を進めます。

ちなみにEC2構成管理は皆無でしたのでこれを機にCDK管理にしようと思います。

1. コンテナ内データバックアップ

特に何も意識せず作ったため、既存のアプリデータはすべてコンテナボリューム内に存在します。
今回はホスト環境(EC2)もまっさらに作り直すため、これらを移行します。

なんてことはないS3を経由したバックアップリストアな方式図

  • busyboxを使ってコンテナ内データをtarで固めます
  • 適当なS3バケットにsyncします
  • 新ホストに展開します

ちなみに新ホスト側ではさすがにデータ保全ちゃんとしないとなーということで、当該データはホストマウントしておこうと心に決めました

busyboxを使ってコンテナ内データをtarで固める

コンテナ内データをホスト間で移行するため、一度コンテナから出してあげます。 やり方は、busyboxという軽量コンテナを使います。

$ docker run --rm --volumes-from $volume \
> -v $(pwd):/backup busybox tar czf /backup/volume.tar.gz /data
  • --rm
    • この操作のためだけのコンテナを起動します。完了後、このコンテナは削除されます
  • --volumes-from $volume
    • 既存のアプリデータが存在するdocker volumeを指定します
  • -v $(pwd):/backup
    • ホスト側のディレクトリをコンテナ内の所定のディレクトリにマウントします
    • 今回だとホスト側 $(pwd) にコンテナ側 /backup をマウントします

これで取得できたら、aws s3 syncとかしてS3バケットに置いてあげます。

2. MongoDBバージョンアップ

前提として、下記が完了してる状態とします。

  • 既存のMongoDBデータベースのバックアップ・リストア
    • 今回はホストマウントして保存していたのでそのままtarで固めて持ってきました

アプリのバージョン更新に伴い、MongoDBもv3.6→v6.0に更新となります。
v3.6→v6.0の場合は、下記のステップを踏む必要があります。
v3.6 → v4.0 → v4.2 → v4.4 → v5.0 → v6.0 

MongoDBの更新には所定の手順があるためこれに従います。
https://www.mongodb.com/docs/manual/release-notes/6.0/
各リリースノートの配下に更新手順が記載されたページがぶら下がっています。 が、大枠としてやることはさほど変わりません。

コンテナで動いているMongoDBの更新手順

  1. docker-compose.ymlに記載されているmongoイメージバージョンを更新可能なバージョンに書き換える
    • ※既存環境はmongo:3.6のため、この場合はmongo:4.0に書き換える
  2. リストアデータをマウントした状態でMongoDBコンテナを起動し、ターミナルログインする
    • docker compose up -d mongo
    • docker compose exec mongo bash
  3. 所定の手順に従い、featureCompatibilityVersion を更新する

MongoDBの更新について、例えばv4.2へ更新する場合は下記の感じです。

MongoDBコンテナを起動して、ターミナルログインする

# execで入ってdbコマンド打つ
ubuntu@ip-10-XX-XX-XX:~/growi$ docker compose up -d mongo
ubuntu@ip-10-XX-XX-XX:~/growi$ docker compose exec mongo bash
root@3b54b02a7e2b:/#

mongoコマンドでDBにログインする

root@3b54b02a7e2b:/# mongo #mongoコマンドでdbにログイン
MongoDB shell version v4.2.24
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("3f8d4b61-446a-42b0-8821-b32388f47801") }
MongoDB server version: 4.2.24
Server has startup warnings:
2023-12-15T02:07:22.022+0000 I  STORAGE  [initandlisten]
2023-12-15T02:07:22.022+0000 I  STORAGE  [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2023-12-15T02:07:22.022+0000 I  STORAGE  [initandlisten] **          See http://dochub.mongodb.org/core/prodnotes-filesystem
2023-12-15T02:07:23.357+0000 I  CONTROL  [initandlisten]
2023-12-15T02:07:23.357+0000 I  CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2023-12-15T02:07:23.357+0000 I  CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2023-12-15T02:07:23.357+0000 I  CONTROL  [initandlisten]
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).

The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.

To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---

FeatureCompatibilityVersionを更新する

> db.adminCommand( { setFeatureCompatibilityVersion: "4.2" } )  # versionを4.2に書き換える
{ "ok" : 1 }
> db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )  # 確認
{ "featureCompatibilityVersion" : { "version" : "4.2" }, "ok" : 1 }
> exit
bye
root@3b54b02a7e2b:/# read escape sequence
ubuntu@ip-10-XX-XX-XX:~/growi$

1点注意点として、 v6.0からDBログイン時の標準コマンドが、mongo ではなく mongosh になるので、ここだけ気をつける必要があります。 ログインしてからの諸々の作業手順はバージョン値以外の変更はないです。

3. ホストOSバージョンを更新する

Ubuntuを使ってホストしていましたが、こちらもだいぶ古かったので、 この機に乗じてUbuntu 18 → Ubuntu 22 に更新します。

SSH鍵の暗号化形式がデフォルトで ED25519 になる

Ubuntu 22 において、SSH鍵のデフォルト暗号化形式が変わります。 このため、RSAで生成した鍵を使ってSSHログインができません。 暗号化強度としてより強固になるようなので、こちらが採用されてるようですが、 既存の鍵で運用したいという甘え 要件があるため、今回こちらは起動時に無理やりRSA形式に戻します。

EC2の構成をCDK管理にする

既存のEC2は手動でぽちぽち構築だったため、これを機にCDK管理にします。 Growiの利用頻度も高くなり、さすがに一人で保守は厳しいと思ったので、 今後複数人で管理できるようにする狙いとして、コード化を進めようと思った次第です。 ネットワーク周りはチーム内で共用のため一旦スコープ外にしました。

というわけでこんなコードになりました。TypeScriptです。

import * as cdk from 'aws-cdk-lib';
import { Vpc } from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
import * as iam from 'aws-cdk-lib/aws-iam';

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    // Look up exist VPC
    const vpc = Vpc.fromLookup(this, "exist-vpc", {
      vpcName: "awesome-vpc",
    });
    // Look up the AMI Id for the Ubuntu 22 Image with CPU Type x86_64
    const amiId = new cdk.CfnParameter(this, "amiId", {
      type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>",
      default: "/aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id"
    });
    // Look up the SecurityGroup for EC2
    const growiSecurityGroup = cdk.aws_ec2.SecurityGroup.fromLookupByName(this, "securitygroup-for-growi", "Growi", vpc);
    // Define IAM Role For EC2
    const ec2IamRole = new cdk.aws_iam.Role(this, "ec2IamRole", {
      assumedBy: new cdk.aws_iam.ServicePrincipal("ec2.amazonaws.com"),
    });
    // Attach S3 full access managed policy to EC2 Role
    ec2IamRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3FullAccess'));
    // Setup UserData
    const userData = cdk.Fn.sub(`#!/bin/bash
    apt-get update
    apt-get install -y ca-certificates curl gnupg unzip
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    chmod a+r /etc/apt/keyrings/docker.gpg
    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
      $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
      tee /etc/apt/sources.list.d/docker.list > /dev/null
    apt-get update
    apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    groupadd docker
    usermod -aG docker ubuntu
    chown ubuntu:docker  /var/run/docker.sock
    curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    unzip awscliv2.zip
    ./aws/install
    echo "PubkeyAcceptedKeyTypes ssh-rsa" | tee -a /etc/ssh/sshd_config
    systemctl restart sshd
    git clone https://github.com/weseek/growi-docker-compose.git /home/ubuntu/growi
    cd /home/ubuntu/growi && mkdir -p ./growi_data ./mongodb ./mongodb/data ./mongodb/config
    chown -R ubuntu:ubuntu /home/ubuntu/growi
    `);
    // Define EC2 Instance 
    const ec2Instance = new cdk.aws_ec2.Instance(this, "ec2Instance", {
      vpc,
      // use Subnets PRIVATE_WITH_EGRESS
      vpcSubnets: { subnetType: cdk.aws_ec2.SubnetType.PRIVATE_WITH_EGRESS },
      // use t3.medium
      instanceType: cdk.aws_ec2.InstanceType.of(cdk.aws_ec2.InstanceClass.T3, cdk.aws_ec2.InstanceSize.MEDIUM),
      machineImage: cdk.aws_ec2.MachineImage.genericLinux({ [this.region]: amiId.valueAsString }),
      keyName: "Growi",
      userData: cdk.aws_ec2.UserData.custom(userData),
      securityGroup: growiSecurityGroup,
      role: ec2IamRole,
      blockDevices: [{
        deviceName: "/dev/sda1",
        volume: cdk.aws_ec2.BlockDeviceVolume.ebs(100),
      }],
    });
  }
}
  • ポイント
    • OS内で必要な処理はすべてUserScriptに記述しました。
      • 何やったかわかりやすいかとおもいまして。
      • (「カスタムAMI作れよ」という声が聞こえてきそう)
    • echo "PubkeyAcceptedKeyTypes ssh-rsa" | sudo tee -a /etc/ssh/sshd_config
    • sudo systemctl restart sshd
      • 前述の通り、RSA形式のSSH鍵認証を許可するため、SSH設定を更新します。
    • cd /home/ubuntu/growi && mkdir -p ./growi_data ./mongodb ./mongodb/data ./mongodb/config
    • chown -R ubuntu:ubuntu /home/ubuntu/growi
      • 不揮発にしたいコンテナ内データは、ホストディレクトリにマウントしておきたいため、このためのディレクトリを作成しています。
      • この時、UserScriptの仕様上 root ユーザ権限でディレクトリが作成されるため、chownで所有者を変更しておきます。

ちなみに大枠はCodeWhispererさんに手伝ってもらいました。

まとめ

MongoDBの更新方法とか、UbuntuのSSHの仕様とか知らないこともあったので非常に勉強になりました!
が、本来はもうちょっとこまめに更新対応していればここまで大掛かりにはならなかったなーという反省点もありますので、
定期的に気にかけてあげることが大事ですねー。

塩漬け、ダメ。ゼッタイ。

執筆者尾澤公亮

しがないインフラエンジニアです
AWSの入門書を執筆したりしてます

Twitter:@jstozw

Amazon 著者ページ:尾澤 公亮:作品一覧、著者略歴 - Amazon.co.jp