ECS のデプロイツール ecspresso と、環境変数を展開して YAML/JSON/TOML を読み込む go-config について

OSS紹介 Advent Calendar 2017 - Qiita 18日目の記事です。(一週間遅れ)

Amazon ECS へのデプロイツール ecspresso と、そこで使っている環境変数を展開しつつ複数の YAML/JSON/TOML を読み込む config loader である go-config の紹介をします。

ecspresso

github.com

エスプレッソ」と読みます。Go で書かれた Amazon ECS 用のデプロイツールです。以下の3つのファイルを用いて ECS へのサービス、タスク定義作成、入れ換えを行います。

  • YAML の設定ファイル
  • タスク定義のための JSON (aws ecs describe-task-definition 出力と互換)
  • サービス定義のための JSON (オプション。aws ecs describe-services 出力の services セクションと互換`)
region: ap-northeast-1
cluster: default
service: app
task_definition: taskdef.json
service_definition: service.json
timeout: 10m

create (サービス作成)、deploy (新しいタスク定義を作成しサービスに対して入れ換える)、status (deployments, events をみる)、rollback (一つ前のタスク定義に差し替えてデプロイすることでロールバックする) の機能があります。

元々 aws-cli を shell script から叩いていたデプロイ script を実装し直したもので (一番最初のバージョンは Go から aws-cli をコマンド起動するものでした)、「それ ○○ (他の ECS に対応したデプロイツール)とどう違うの」といわれると答えに窮する代物ですが…

無理矢理特徴を挙げると

  • Go で実装してあるのでシングルバイナリで環境依存少なく動作する
  • タスクとサービスの定義ファイルには後述の go-config による、環境変数展開機能がある

ぐらいでしょうか。

go-config

github.com

こちらは、複数の YAML / JSON / TOML をマージしつつ、Go の text/template の記法で環境変数を展開して読み込むことができるパッケージです。もともと社内のとあるアプリケーションで使用するために開発されたものですが、ecspresso で使いたいがために (便利なので) 公開しました。

コンテナでアプリケーションやミドルウェアを動作させる場合、設定ファイル自体はコンテナに同梱しておくが、その中の値は環境変数で指定したい、というニーズがあります。ファイルにハードコードされていると設定値を書き換えるたびにコンテナのビルドが必要になりますが、環境変数であればコンテナの起動時に動的に指定することができるため、便利になりますね。

foo: {{ env "FOO" "default_foo" }}

このような YAML を、環境変数 FOObar が設定されている状態で読み込むと

foo: bar

となりますし、FOO が未設定であればデフォルト値として

foo: default_foo

として読み込まれます。

また、環境変数が設定されていなければ panic することで、読みこみ時に確実に設定されていることを強要する {{ must_env "FOO" }} という記法もあります。

やっていることは単純で、先に Go の text/template環境変数を展開した上で YAML / JSON / TOML のパーサを通しているだけなので、ファイル自体が各フォーマットとして不正であっても展開後に正しい状態であれば、問題なく読み込むことができます。

とはいえエディタでの編集時にそれぞれのフォーマットとして正しく扱えてくれた方が楽なので、" " でクォートされた内部に展開する場合は、template の記法の方でバッククォートを用いるのがお薦めです。

{
  "foo": "{{ env `FOO` `default_foo` }}"
}

先述の ecspresso では、タスクとサービスの定義ファイル (JSON) の読みこみ時に go-config による環境変数展開が行われます。

{
  "taskDefinition": {
    "containerDefinitions": [
      {
         "image": "example.com/myapp:{{ must_env `IMAGE_TAG` }}",
         "environment": [
           {
             "name": "APP_SECRET",
             "value": "{{ must_env `APP_SECRET` }}"
           },
           {
             "name": "ENDPOINT",
             "value": "{{ env `ENDPOINT` `example.com` }}"
           }
         ]
      }
    ]
  }
}

典型的には、以下のような値を展開することを想定しています。

  • デプロイごとに変わる可能性が高い値
    • Docker イメージのタグなど
  • リポジトリには生で入れたくないクレデンシャルの類

特にクレデンシャル類は、direnv 等で使用される .envrc というファイルに記述した上で暗号化してリポジトリに保存し、デプロイ時に復号、環境変数に設定した上でデプロイを行うと便利に扱えると思います。

# .envrc
export APP_TOKEN="raw_value_of_token"

AWS KMS で暗号化した値を .envrc.encrypted として出力、リポジトリにはこれをコミット。

$ aws kms encrypt --key-id mykey \
       --plaintext fileb://.envrc \
       --output text --query CiphertextBlob \
       --output text \
    | base64 --decode > .envrc.encrypted

デプロイ直前に KMS で .envrc を復号し、環境変数に設定してデプロイ。

$ aws kms decrypt --ciphertext-blob fileb://.envrc.encrypted \
        --output text
        --query Plaintext \
     | base64 --decode > .envrc
$ source .envrc
$ ecspresso --config app.yaml deploy

まとめ

  • Amazon ECS 用のデプロイツール ecspresso を書きました
  • go-config は実行時に環境変数を展開しつつ設定ファイルを読み込めて便利です