Mattermost を Amazon Lightsail Container Service に載せる
前回の記事
これまで Slack の代わりに Mattermost を導入できないか試してきましたが、スマホアプリを使うには HTTPS API 通信が必須のようなので、さらに AWS 環境への導入を試してみます。
Slack から乗り替えるにあたりコストを重視していますので、今回は AWS のサービスで比較的安価で簡単に導入できそうな Lightsail を利用します。
Lightsail には EC2, S3, RDS, Fargate のようなインスタンス、ストレージ、マネージドデータベース、コンテナサービスが用意されているようです。
永続的なデータを保持するには、インスタンスにまとめてインストールするか、ストレージとデータベースを別立てにしてサービス本体はコンテナサービスを利用する等があると思います。
前回までの記事で docker compose の設定は目処がついているので、インスタンスサービスに Docker を導入する方法が一番シンプルだと思いますが、今回はあえてコンテナサービスで動作確認をしてみます。
ストレージとデータベースの永続化は次回にして、まずはデータ非永続化での動作環境を構築します。
構築手順
1. Amazon Lightsai のコンテナ作成
初めの 3 ヶ月無料の Micro プランを選択しました。
コンテナサービスの名称は、container-service-limited-20221031
としました。
コンテナサービスが作成されます。
2. AWS CLI と lightsailctl プラグイン の導入
Lightsail CLI コマンドを実行するため、こちらを参考に AWS CLI と lightsailctl プラグインを導入します。
3. CI/CD の設定
コンテナサービスは初めて使いますが、どうやら docker compose には対応していないようで、ローカルで作成した docker image をアップロードする形で構築するようです。
docker-compose.yml で設定していた項目を Docker file に記載する必要がありそうですね。
今回は GitHub Actions を利用して git push からデプロイまでの一連の作業を実行できるようにしました。
1. GitHub の Secrets 設定
GitHub Actions から AWS サービスにアクセスできるよう、GitHub Repository の Secrets に AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY を設定します。
2. Dockerfile の作成
docker compose ではコンテナ間通信はサブネットが組まれますが、Lightsail container service では同じサービスに共存するコンテナ間はローカルホスト 127.0.0.1
接続となるようです。
前回作成した docker-compose.yml の設定では pgAdmin と nginx のポートが被るため、今回は次の三つの Docker image で構成します。
- postgres:14
- nginx:latest
- mattermost/mattermost-enterprise-edition:7.1.2
このうち、postgres と nginx は Dockerfile で構成します。
ARG POSTGRES_IMAGE_TAG
FROM postgres:${POSTGRES_IMAGE_TAG}
ARG DB_LANG=en_US # docker-compose.ymlから上書き
RUN localedef -i $DB_LANG -c -f UTF-8 -A /usr/share/locale/locale.alias $DB_LANG.UTF-8 # ロケール追加
ENV LANG $DB_LANG.utf8
EXPOSE 5432/tcp
# 初期スクリプトを追加
ADD ./postgresql/init-script/* /docker-entrypoint-initdb.d
ARG NGINX_IMAGE_TAG
FROM nginx:${NGINX_IMAGE_TAG}
EXPOSE 80/tcp
# 設定ファイルを追加
ADD ./nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
# 検証時
# CMD ["nginx-debug", "-g", "daemon off;"]
3. コンテナ起動コマンド作成
json 形式でコンテナを起動するコマンドテンプレートを出力します。
% aws lightsail create-container-service-deployment --generate-cli-skeleton
{
"serviceName": "",
"containers": {
"KeyName": {
"image": "",
"command": [
""
],
"environment": {
"KeyName": ""
},
"ports": {
"KeyName": "TCP"
}
}
},
"publicEndpoint": {
"containerName": "",
"containerPort": 0,
"healthCheck": {
"healthyThreshold": 0,
"unhealthyThreshold": 0,
"timeoutSeconds": 0,
"intervalSeconds": 0,
"path": "",
"successCodes": ""
}
}
}
出力を元にコマンドファイルを作成します。
{
"serviceName": "",
"containers": {
"db": {
"image": "",
"command": [
],
"environment": {
"DB_LANG": "ja_JP",
"POSTGRES_USER": "",
"POSTGRES_PASSWORD": "",
"POSTGRES_DB": ""
},
"ports": {
"5432": "TCP"
}
},
"mattermost": {
"image": "",
"command": [
],
"environment": {
"TZ": "",
"MM_SQLSETTINGS_DRIVERNAME": "",
"MM_SQLSETTINGS_DATASOURCE": "",
"MM_SERVICESETTINGS_SITEURL": "",
"MM_SERVICESETTINGS_ENABLEAPICHANNELDELETION": "",
"MM_SERVICESETTINGS_ENABLETUTORIAL": "",
"MM_LOCALIZATIONSETTINGS_DEFAULTSERVERLOCALE": "",
"MM_LOCALIZATIONSETTINGS_DEFAULTCLIENTLOCALE": "",
"MM_TEAMSETTINGS_MAXUSERSPERTEAM": "",
"MM_TEAMSETTINGS_MAXCHANNELSPERTEAM": "",
"MM_PLUGINSETTINGS_ENABLE": "",
"MM_PLUGINSETTINGS_ENABLEUPLOADS": "",
"MM_BLEVESETTINGS_INDEXDIR": "",
"MM_BLEVESETTINGS_ENABLEINDEXING": "",
"MM_BLEVESETTINGS_ENABLESEARCHING": "",
"MM_BLEVESETTINGS_ENABLEAUTOCOMPLETE": ""
},
"ports": {
"8065": "HTTP"
}
},
"nginx": {
"image": "",
"command": [
],
"environment": {
},
"ports": {
"80": "HTTP"
}
}
},
"publicEndpoint": {
"containerName": "nginx",
"containerPort": 80,
"healthCheck": {
"healthyThreshold": 0,
"unhealthyThreshold": 0,
"timeoutSeconds": 30,
"intervalSeconds": 60,
"path": "",
"successCodes": ""
}
}
}
4. GitHub Actions の設定
GitHub に push したら Docker build が走り、作成された Docker image が Lightsail Containers にデプロイされるよう Workflow を設定します。
name: Build and deploy Docker app to Lightsail
on:
push:
branches:
- main
env:
AWS_REGION: ap-northeast-1
AWS_LIGHTSAIL_SERVICE_NAME: container-service-limited-20221031
jobs:
deploy:
name: Build and deploy
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Utilities
run: |
sudo apt-get update
sudo apt-get install -y jq unzip
- name: Install AWS Client and LightsailControl Plugin
run: |
curl https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o awscliv2.zip
unzip awscliv2.zip
sudo ./aws/install || true
aws --version
curl https://s3.us-west-2.amazonaws.com/lightsailctl/latest/linux-amd64/lightsailctl -o lightsailctl
sudo mv lightsailctl /usr/local/bin/lightsailctl
sudo chmod +x /usr/local/bin/lightsailctl
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{env.AWS_REGION}}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Pull and Build Docker Image
run: |
docker pull mattermost/mattermost-enterprise-edition:7.1.2
docker compose build
docker image ls
- name: Push Image
run: |
service_name=${{env.AWS_LIGHTSAIL_SERVICE_NAME}}
aws lightsail push-container-image \
--region ${{env.AWS_REGION}} \
--service-name ${service_name} \
--label mmapp-db \
--image mmapp-db
aws lightsail push-container-image \
--region ${{env.AWS_REGION}} \
--service-name ${service_name} \
--label mattermost \
--image mattermost/mattermost-enterprise-edition:7.1.2
aws lightsail push-container-image \
--region ${{env.AWS_REGION}} \
--service-name ${service_name} \
--label mmapp-nginx \
--image mmapp-nginx
- name: Make deploy setting
run: |
export $(cat .env | grep -v '^#' | xargs)
service_name=${{env.AWS_LIGHTSAIL_SERVICE_NAME}}
aws lightsail get-container-images --service-name ${service_name} | jq --raw-output '[.containerImages[] | select(.image | contains("mmapp-db")) | .image][0]' > db_image.txt
aws lightsail get-container-images --service-name ${service_name} | jq --raw-output '[.containerImages[] | select(.image | contains("mattermost")) | .image][0]' > mm_image.txt
aws lightsail get-container-images --service-name ${service_name} | jq --raw-output '[.containerImages[] | select(.image | contains("mmapp-nginx")) | .image][0]' > nginx_image.txt
cat container.template.json \
| jq --arg service_name ${service_name} '.serviceName=$service_name' \
| jq --arg db_image $(cat db_image.txt) '.containers.db.image=$db_image' \
| jq --arg mm_image $(cat mm_image.txt) '.containers.mattermost.image=$mm_image' \
| jq --arg nginx_image $(cat nginx_image.txt) '.containers.nginx.image=$nginx_image' \
| jq --arg POSTGRES_USER $POSTGRES_USER '.containers.db.environment.POSTGRES_USER=$POSTGRES_USER' \
| jq --arg POSTGRES_PASSWORD $POSTGRES_PASSWORD '.containers.db.environment.POSTGRES_PASSWORD=$POSTGRES_PASSWORD' \
| jq --arg POSTGRES_DB $POSTGRES_DB '.containers.db.environment.POSTGRES_DB=$POSTGRES_DB' \
| jq --arg TZ $TZ '.containers.mattermost.environment.TZ=$TZ' \
| jq --arg MM_SQLSETTINGS_DRIVERNAME $MM_SQLSETTINGS_DRIVERNAME '.containers.mattermost.environment.MM_SQLSETTINGS_DRIVERNAME=$MM_SQLSETTINGS_DRIVERNAME' \
| jq --arg POSTGRES_USER $POSTGRES_USER --arg POSTGRES_PASSWORD_ENCODED $POSTGRES_PASSWORD_ENCODED --arg POSTGRES_DB $POSTGRES_DB '.containers.mattermost.environment.MM_SQLSETTINGS_DATASOURCE="postgres://'$POSTGRES_USER':'$POSTGRES_PASSWORD_ENCODED'@localhost:5432/'$POSTGRES_DB'?sslmode=disable&connect_timeout=10"' \
| jq --arg MM_SERVICESETTINGS_SITEURL $MM_SERVICESETTINGS_SITEURL '.containers.mattermost.environment.MM_SERVICESETTINGS_SITEURL=$MM_SERVICESETTINGS_SITEURL' \
| jq --arg MM_SERVICESETTINGS_ENABLEAPICHANNELDELETION $MM_SERVICESETTINGS_ENABLEAPICHANNELDELETION '.containers.mattermost.environment.MM_SERVICESETTINGS_ENABLEAPICHANNELDELETION=$MM_SERVICESETTINGS_ENABLEAPICHANNELDELETION' \
| jq --arg MM_SERVICESETTINGS_ENABLETUTORIAL $MM_SERVICESETTINGS_ENABLETUTORIAL '.containers.mattermost.environment.MM_SERVICESETTINGS_ENABLETUTORIAL=$MM_SERVICESETTINGS_ENABLETUTORIAL' \
| jq --arg MM_LOCALIZATIONSETTINGS_DEFAULTSERVERLOCALE $MM_LOCALIZATIONSETTINGS_DEFAULTSERVERLOCALE '.containers.mattermost.environment.MM_LOCALIZATIONSETTINGS_DEFAULTSERVERLOCALE=$MM_LOCALIZATIONSETTINGS_DEFAULTSERVERLOCALE' \
| jq --arg MM_LOCALIZATIONSETTINGS_DEFAULTCLIENTLOCALE $MM_LOCALIZATIONSETTINGS_DEFAULTCLIENTLOCALE '.containers.mattermost.environment.MM_LOCALIZATIONSETTINGS_DEFAULTCLIENTLOCALE=$MM_LOCALIZATIONSETTINGS_DEFAULTCLIENTLOCALE' \
| jq --arg MM_TEAMSETTINGS_MAXUSERSPERTEAM $MM_TEAMSETTINGS_MAXUSERSPERTEAM '.containers.mattermost.environment.MM_TEAMSETTINGS_MAXUSERSPERTEAM=$MM_TEAMSETTINGS_MAXUSERSPERTEAM' \
| jq --arg MM_TEAMSETTINGS_MAXCHANNELSPERTEAM $MM_TEAMSETTINGS_MAXCHANNELSPERTEAM '.containers.mattermost.environment.MM_TEAMSETTINGS_MAXCHANNELSPERTEAM=$MM_TEAMSETTINGS_MAXCHANNELSPERTEAM' \
| jq --arg MM_PLUGINSETTINGS_ENABLE $MM_PLUGINSETTINGS_ENABLE '.containers.mattermost.environment.MM_PLUGINSETTINGS_ENABLE=$MM_PLUGINSETTINGS_ENABLE' \
| jq --arg MM_PLUGINSETTINGS_ENABLEUPLOADS $MM_PLUGINSETTINGS_ENABLEUPLOADS '.containers.mattermost.environment.MM_PLUGINSETTINGS_ENABLEUPLOADS=$MM_PLUGINSETTINGS_ENABLEUPLOADS' \
| jq --arg MM_BLEVESETTINGS_INDEXDIR $MM_BLEVESETTINGS_INDEXDIR '.containers.mattermost.environment.MM_BLEVESETTINGS_INDEXDIR=$MM_BLEVESETTINGS_INDEXDIR' \
| jq --arg MM_BLEVESETTINGS_ENABLEINDEXING $MM_BLEVESETTINGS_ENABLEINDEXING '.containers.mattermost.environment.MM_BLEVESETTINGS_ENABLEINDEXING=$MM_BLEVESETTINGS_ENABLEINDEXING' \
| jq --arg MM_BLEVESETTINGS_ENABLESEARCHING $MM_BLEVESETTINGS_ENABLESEARCHING '.containers.mattermost.environment.MM_BLEVESETTINGS_ENABLESEARCHING=$MM_BLEVESETTINGS_ENABLESEARCHING' \
| jq --arg MM_BLEVESETTINGS_ENABLEAUTOCOMPLETE $MM_BLEVESETTINGS_ENABLEAUTOCOMPLETE '.containers.mattermost.environment.MM_BLEVESETTINGS_ENABLEAUTOCOMPLETE=$MM_BLEVESETTINGS_ENABLEAUTOCOMPLETE' \
> container.json
cat container.json
- name: Deploy
run: |
aws lightsail create-container-service-deployment --service-name ${{env.AWS_LIGHTSAIL_SERVICE_NAME}} --cli-input-json file://$(pwd)/container.json
ちょっと環境変数を足しすぎましたね。上手くデプロイできなかったりして、ここでだいぶ詰まりました。ポイントは、.env
で設定した環境変数を export $(cat .env | grep -v '^#' | xargs)
で読込み、jq
で コマンドテンプレート container.template.json
に設定して出力していることです。
5. push & プロビジョニング
これらファイルを git push すると workflow が働き、Lightsail デプロイが実行され、Lightsail container に アクセスすると Mattermost の初期画面に繋がります。
まとめ
GitHub Actions と Lightsail の動作仕様の把握に手間取りましたが、一度構成ができればワークフローで git push するだけでデプロイ出来るのはとても便利ですね。Lightsail では SSL も自動的に適用されるため、Let's Encrypt などを組み込む必要もありませんでした。
コンテナサービスはステートレスで動作するためデータは保持されないため、次回はさらなる本稼働に向けてデータ永続化などを試行してみます。
手間取って疲れたので、この辺で!
詳しくはソースファイルをご覧ください。
参考 (感謝です!)
Discussion