OpenProject on AWS ECS その2

# OpenProjectが公開しているdocker-compose.ymlをAWS ECSにデプロイしてみました その2

以前[OpenProject on AWS ECS](https://qiita.com/runaway_cat/items/cf92034e0c65e19cc860)という記事で、ecs-cliを使ってECSにOpenProjectをデプロイしてみたんですが、今回は同じことをdockerのECS拡張を使ってやってみました。
OpenProjectのもともとの構成ではmemcachedとpostgresqlもコンテナ動作させてますが、そこは個人の趣味でマネージドサービスを使用しています。

## dockerのECS拡張とは
2020/7に発表された拡張機能です。[AWS and Docker collaborate to simplify the developer experience](https://aws.amazon.com/jp/blogs/containers/aws-docker-collaborate-simplify-developer-experience/)にあるとおり、dockerクライアントで直接ECS上のコンテナを操作できるという代物です。docker-composeも使用可能になってます。
2020/11/19にGA到達しました。[Docker Compose for Amazon ECS Now Available](https://www.docker.com/blog/docker-compose-for-amazon-ecs-now-available/?utm_source=docker&utm_medium=organic%20search&utm_campaign=blog%20post&utm_budget=)

## 以前のデプロイ方法(ecs-cli)の困ったところまとめ

- ecs-cliがdocker-composeのver3.xに対応していないため、凝った作りのdocker-compose.ymlの場合はかなりの部分を編集しなければならない
- fargateへのデプロイが困難
- コンテナ間依存の記述が困難


## コマンド実行環境インストール概要

[Deploying Docker containers on ECS](https://docs.docker.com/engine/context/ecs-integration/)の通り作業して、コマンド実行環境を作ります。

### awscli

Ubuntu 20.04でコマンド実行環境を作りました。まずaws cliをインストールし、credential設定します。
なお、以下のIAM権限が必要でした。

- EC2
- IAM
- ElasticLoadBalancing
- CloudMap
- ElasticFileSystem
- CloudFormation

その他に、以下の権限も与えてましたが、docker compose upに必要かどうかは未確認です。

- ECS
- CodeDeploy
- CloudWatchLogsFullAccess
 
### docker-cli

docker-ceおよびdocker-composeをインストールしました。
マニュアルにあるとおり、AWS contextを設定してやる必要があります。

## その他AWS環境

### postgresqlとmemcached

リージョン内にpostgresqlとmemcachedのマネージドサービスを作成しておきます。postgresqlのユーザとパスワードは、docker-compose.yml記載のものをそのまま使って作成しました。
接続先情報はdocker-compose.ymlに記述します。

### default VPC

default VPCがリージョンに存在している必要があります。普通あるとは思いますが、何かの理由で消してしまった場合は作成しておいてください。

## docker-compose-yml

dockerのECS拡張では、docker-compose.ymlファイルをcloudformationに食わせるように変換して、cloudformation経由でECS環境を作るということをやっています。

まず、[OpenProjectのマニュアル通り](https://docs.openproject.org/installation-and-operations/installation/docker/#one-container-per-process-recommended)docker-compose.ymlファイルを入手します。

~~~shell
> git clone https://github.com/opf/openproject-deploy --depth=1 --branch=stable/11 openproject
~~~

このファイルに対して修正します。
最終的に使用したdocker-compose.ymlは以下の通りとなりました。

~~~file:docker-compose.yml
version: "3.7"                                                                                                                                                                                       
                                                                                                                                                                                                     
networks:                                                                                                                                                                                            
  frontend:                                                                                                                                                                                          
  backend:                                                                                                                                                                                           
                                                                                                                                                                                                     
volumes:                                                                                                                                                                                             
  opdata:                                                                                                                                                                                            
                                                                                                                                                                                                     
x-op-restart-policy: &restart_policy                                                                                                                                                                 
  restart: unless-stopped                                                                                                                                                                            
x-op-image: &image                                                                                                                                                                                   
  image: openproject/community:${TAG:-11}                                                                                                                                                            
x-op-app: &app                                                                                                                                                                                       
  <<: *image                                                                                                                                                                                         
  <<: *restart_policy                                                                                                                                                                                
  environment:                                                                                                                                                                                       
    RAILS_CACHE_STORE: "memcache"                                                                                                                                                                    
    OPENPROJECT_CACHE__MEMCACHE__SERVER: "cache.yyyy.cfg.usw2.cache.amazonaws.com:11211"                                                                                                           
    OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}"                                                                                                            
    DATABASE_URL: "postgres://postgres:p4ssw0rd@openproject.xxxxx.us-west-2.rds.amazonaws.com/openproject"                                                                                    
    USE_PUMA: "true"                                                                                                                                                                                 
    # set to true to enable the email receiving feature. See ./docker/cron for more options                                                                                                          
    IMAP_ENABLED: "${IMAP_ENABLED:-false}"                                                                                                                                                           
  volumes:                                                                                                                                                                                           
    - "opdata:/var/openproject/assets"                                                                                                                                                               

services:                                                                                                                                                                                            
  proxy:                                                                                                                                                                                             
    <<: *image                                                                                                                                                                                       
    <<: *restart_policy                                                                                                                                                                              
    command: "./docker/proxy"                                                                                                                                                                        
    ports:                                                                                                                                                                                           
      - "${PORT:-8080}:80"                                                                                                                                                                           
    environment:
      APP_HOST: web
      OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}"
    depends_on:
      - web
    networks:
      - frontend

  web:
    <<: *app
    command: "./docker/web"
    networks:
      - frontend
      - backend
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 2048M

  worker:
    <<: *app
    command: "/bin/sh -c './docker/worker && /bin/sh'"
    networks:
      - backend

  cron:
    <<: *app
    command: "./docker/cron"
    networks:
      - backend
~~~

3.7の記法をそのまま使えるため、大幅に楽になりました。
オリジナルとの差分は以下の通りです。

- dbコンテナ削除に伴う対応
    - volume削除
    - 接続先設定変更
    - 依存関係でのdb削除
    - service dbの削除
- cacheコンテナ削除に伴う対応
    - 接続先設定変更
    - 依存関係でのcache削除
    - service cacheの削除
- seederコンテナ削除に伴う対応
    - service seederの削除
    - 依存関係でのseeder削除
- essential設定が出来ないことへの対応
    - workerコンテナのコマンドを修正 
- webコンテナの使用リソース設定
    - CPUとメモリの設定を追加

下の３つについて説明します。

「seederコンテナ削除に伴う対応」
seederコンテナというのは、compose up時に動作してDBの初期化を行ったのち、常駐せずにexitするコンテナです。
ECS環境ではこういうコンテナにはessential = falseという設定を入れてやらないといけないのですが、docker-compose.ymlへのessential設定の方法が[マニュアルやサンプル](https://github.com/docker/compose-cli/blob/main/docs/ecs-compose-examples.md)で説明されていません。
また、seederがモタモタ動いているうちに環境全体が終了してしまうという事態が頻発したため、DB設定のためseederだけ動かすdocker-compose.ymlを作成したあと、上に引用のdocker-compose.ymlを使って最終的な環境を作るということにしました。

「essential設定が出来ないことへの対応」
上述の通り、seederコンテナは常駐しないのですが、他にもそういう動作をしているらしきコンテナ(worker)がいます。終了してしまわないように、コマンドに手を入れています。(不要かもです)

「webコンテナの使用リソース設定」
デフォルトで各コンテナはCPU 256ms/Memory 512MBというlimit設定で起動してきますが、これだとWebコンテナがまともに動作しなかったため、このコンテナだけは多めにリソースを積みました。記法は[サンプル](https://github.com/docker/compose-cli/blob/main/docs/ecs-compose-examples.md)の通りです。


### その他

#### 環境変数
デフォルトではproxyコンテナは8080番をbindしているんですが、fargateでは？動作しませんでしたので、docker compose up実行時に以下の環境変数を設定して、コンテナと同一ポートを使用するようにしました。

~~~shell
> set -x PORT 80 
~~~

#### コンバート

cloudformationに食わせているファイルは、以下の方法で確認できます。

~~~shell
> docker compose convert
~~~

## まとめ

オリジナルに対してかなり手をいれてますが環境設定のための編集がほとんどで、基本的には3.x記法をそのまま使えるためecs-cliを使うよりはかなり楽になりました。
[以前のデプロイ方法の困ったところまとめ](https://qiita.com/runaway_cat/items/168b5a35d513c61cbf05#%E4%BB%A5%E5%89%8D%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E6%96%B9%E6%B3%95%E3%81%AE%E5%9B%B0%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%93%E3%82%8D%E3%81%BE%E3%81%A8%E3%82%81)はクリア出来てます。
docker-compose.ymlを使ってECSにサービスをデプロイしたい場合はぜひ試してみてください。