LIVESENSE made*

リブセンスのエンジニアやデザイナーの活動や注目していることをまとめたブログです。

MENU

転職会議のECSデプロイ事情

こんにちは、転職会議でプログラマをやっている山内です。 皆さんはDockerを使っていますか? 転職会議では、AWSに移行する際に一部のアプリケーションにおいてDockerを採用しました。

AWS上でDockerコンテナを動かすのにECSを利用しています。 今日は転職会議のECSへのDockerデプロイツールであるpnzrについて紹介します。

当初の運用方式

転職会議はAWS移行した際にDockerを導入しました。 AWSが公式で用意しているAWSコンソールには、最低限の機能しか用意されていません。 このためDockerコンテナのデプロイはchat botとlambdaを利用して行っていました。

デプロイに利用するtask definition templateにはデータベースのパスワードなどの秘密情報が含まれるため、githubのリポジトリに含めることができません。 そこで、秘密情報はS3に設置するようにしました。

当初のデプロイ方式をfig1に示します。

f:id:ieee0824x:20170526140742p:plain
fig1: 当初のデプロイ方式

この構成ではいくつか問題が有りました。

まずtask definitionは日々複数の開発者によって更新されるため、なるべくgitによるバージョン管理を行いたいです。 またS3に置いているとはいえ、秘密情報を平文で保存するのはセキュリティ上よくありません。 理想的には以下のような方式でtask definitionの管理とデプロイをできるとよさそうです。

f:id:ieee0824x:20170526140825p:plain
fig2: 理想的なデプロイ方式

何か良いものは無いか検討した

いろいろ下調べをした上でやりたいことはhakoが近かったです。 ですが以下のような点があったので採用しませんでした。

  • 自分たちでhakoを管理しようと思ったらドキュメントが少ない
  • 設定の暗号化をしたい
  • 複数のAWSアカウント(本番環境/検証環境)へのデプロイをやりたい
  • ELBの管理はTerraformでやるので必要ない

作ったもの

前述のようなことを行うため、Go言語製のコマンドラインツール pnzr (“ぱんつぁー”)を作りました。

転職会議ではchat botからpnzrを呼び出すことでデプロイを行っています。

f:id:ieee0824x:20170526140853p:plain
fig3: pnzrのデプロイ方式

このpnzrはECSのServiceとtask definitionの管理だけを行うという方針で設計されており、ELBやクラスターの管理は行いません。 ELBの管理も原理的に不可能ではありませんでしたが、ELBは別途Terraformで管理をする予定だったので含めませんでした。

pnzrには次の5つの機能があります。

  • ECSのデプロイ
  • 設定の分割管理
  • 設定の暗号化
  • 暗号化されたファイルの確認
  • 暗号化されたファイルの編集

基本的な設定項目はECSのtask definitionの項目に依存します。 実装の都合により、jsonのキーはキャメルケースにしておく必要があります。

pnzrで簡単なアプリケーションをデプロイする

例えば test-cluster という名前のクラスターに sample という名前のサービスをデプロイするとします。 その時の設定を sample.json という名前で保存したと仮定します。 その時の設定を例に示すと以下のようになります。

{
    "ECS":{
        "Service":{
            "Cluster":"test-cluster",
            "DeploymentConfiguration":{
                "MaximumPercent":200,
                "MinimumHealthyPercent":50
            },
            "DesiredCount":1,
            "LoadBalancers":[
                {
                    "ContainerName":"sample",
                    "ContainerPort":80,
                    "TargetGroupArn":"taget group の arn"
                }
            ],
            "Role":"ecsServiceRole",
            "ServiceName":"sample",
            "TaskDefinition":"sample-app"
        },
        "TaskDefinition":{
            "ContainerDefinitions":[
                {
                    "Cpu":0,
                    "Essential":true,
                    "Image":"ieee0824/dummy-app:latest",
                    "MemoryReservation":2048,
                    "Name":"sample-app",
                    "PortMappings":[
                        {
                            "HostPort":0,
                            "ContainerPort":8080,
                            "Protocol":"tcp"
                        }
                    ]
                }
            ],
            "Family":"sample",
            "NetworkMode":"bridge",
            "TaskRoleArn":"task role の arn"
        }
    }
}

前述の通りpnzrではロードバランサーの管理は想定していません。 ロードバランサーとtarget groupは予め何らかの方法で用意してください。

ロードバランサー周辺の設定は少しわかりづらいので説明しておきます。 ECSにおいてデプロイされたコンテナがロードバランサー紐づく時target groupを利用して紐付けられます。 なのでpnzrでデプロイする前にロードバランサーを作成しておき設定にarnを記述する必要があります。 ロードバランサーのarnに紐づくわけではないことにご注意ください。

"LoadBalancers":[
    {
        "ContainerName":"sample",
        "ContainerPort":80,
        "TargetGroupArn": "taget group の arn"
    }
]

おおよそのデプロイするための準備はこれで完了です。 pnzrにdeployオプションと先ほど作った設定ファイル名を渡すことでデプロイできます。

$ pnzr deploy sample.json

ECSの設定項目は多岐にわたるのでここに書いてない設定項目は多々あります。 設定項目はほぼtask definition通りなので、先に手動で設定を行って確認し、それをもとにjsonを書くとわかりやすいと思います。

設定ファイルを分割する

pnzrでは設定ファイルを分割することができます。 例えば本番環境と検証環境を分けてデプロイする時環境に依存する項目だけを切り出して管理するのに役立ちます。

まず本体となる設定ファイルを main.json とします。 main.jsonの中身を次のようにします。

{
    "ECS":{
        "TaskDefinition":{
            "ContainerDefinitions":[
                "Environment": $env
            ]
        }
    }
}

$env と書かれたjsonのフォーマットにそぐわないものが登場しました。 $env はpnzrの設定ファイルにおける変数です。

本体となる設定ファイルを作ったら次に本体に埋め込まれる情報を仕込んだファイルを作ります。 次のようなファイルを vars/config.json として作ります。

{
    "env" : [
        {
            "Name": "FOO",
            "Value": "var"
        }
    ]
}

デプロイのタイミングで本体の設定ファイルと分割設定の入ったディレクトリを指定することで設定が埋め込まれます。

$ pnzr deploy -f main.json -vars_path vars/

設定が埋め込まれることによって次のような設定としてデプロイされます。

{
    "ECS":{
        "TaskDefinition":{
            "ContainerDefinitions":[
                "Environment": [
                    {
                        "Name": "FOO",
                        "Value": "var"
                    }
                ]
            ]
        }
    }
}

この時 vars ディレクトリの中は config.json のみでしたが複数のjsonが混在していても問題ありません。

pnzrで秘密情報を扱う

pnzrは設定ファイルの暗号化に対応しています。 Ansibleでいうところのvaultのようなものです。 pnzrにおいて設定の暗号化は vault オプションを利用します。

たとえば以下のような secure.json を暗号化してみます。

$ pnzr vault -encrypt -key_id ${KMS_KEY_ID} secure.json
{
    "db_password": "foo",
    
}

暗号化すると次のようになります

{"type":"kms","cipher":"EQECAHh5q0tFgkoZe9C6czjL/QJ6+DlDwjLL6N3YmGIcYUKyuwAAAKkwgaYGCSqGSIb3DQEHBqCBmDCBlQIBADCBjwYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxLgpVvtPehqdE3J5YOytNu974QzkXRoMqhU1OUfRnI413s9W7iqs5LB8n2CjIDd0OxsHgUMb2F25tb+B0f5OBgPDvQ/VL/sj4hJwpbXjkTdebIaeFA16b4A3gFaPNQmHF"}

暗号化した設定は平文の設定ファイルと同じように扱うことができます。 暗号化したものをデプロイするときはkms key idをデプロイ時に指定します。 kms key idを指定すること意外は平文をデプロイする時と変わりません。 pnzrが自動的に暗号化したファイルを復号してECSにデプロイします。

# varsは平文と暗号文の入ったディレクトリ
$ pnzr deploy -vars_path vars/ -key_id ${KMS_KEY_ID} main.json

f:id:ieee0824x:20170526140931p:plain
fig4: 暗号化したファイルをデプロイする図

暗号化した内容を編集したいときは vault-edit オプションを使用することで安全に編集することができます。 vault-editは環境変数に依存して起動するエディタが決定されます。
詳細はwikiをご覧ください。

さいごに

以上、簡単に転職会議でのDockerの運用についてご紹介しました。

些細な事ですがエンジニアのお仕事を楽にできれば幸いです。