Amazon ECSでアプリケーションを動かす


環境変数の管理

ECSでは、タスク定義の環境変数にセキュリティな情報を設定するのは推奨されてない。
http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definition_environment

認証情報データなどの機密情報にプレーンテキストの環境変数を使用することはお勧めしません。

そこでAmazon EC2 Systems Managerのパラメーターストアで管理することにした。
パラメーターストアを使うことでKMSによる暗号化して管理できる。
コンテナがAWS環境のみでしか動かせなくなってしまうため、PARAMETER_STORE_PREFIXが設定されてる場合のみパラメーターストアで取得するようにした。

docker-entrypoint.sh
#!/bin/bash

set -e

PARAMETER_STORE_PREFIX=${PARAMETER_STORE_PREFIX:-}

if [ -n "$PARAMETER_STORE_PREFIX" ]; then
  export SECRET_KEY_BASE=$(aws ssm get-parameters --name ${PARAMETER_STORE_PREFIX}.secret.key.base --with-decryption --query "Parameters[0].Value" --output text)
  export DATABASE_URL=$(aws ssm get-parameters --name ${PARAMETER_STORE_PREFIX}.database.url --no-with-decryption --query "Parameters[0].Value" --output text)
  export RAILS_LOG_TO_STDOUT=true
  export RAILS_SERVE_STATIC_FILES=true
fi

exec "$@"
.dockerignore
.bundle

log
tmp

node_modules
yarn-error.log

.byebug_history

app/assets/javascripts/bundle.js
config/database.yml
.env
.env.app
.env.db
.git
Dockerfile
FROM alpine:3.7

ENV RAILS_ENV="production" \
    NODE_ENV="production" \
    NPM_CONFIG_PRODUCTION="false" \
    RUNTIME_PACKAGES="bash ruby ruby-irb ruby-json ruby-rake ruby-bigdecimal ruby-io-console ruby-dev nodejs yarn libxml2-dev libxslt-dev mariadb-client-libs tzdata py-pip" \
    DEV_PACKAGES="build-base mariadb-dev"

RUN apk add --update --no-cache $RUNTIME_PACKAGES && \
    pip install --no-cache-dir awscli && \
    mkdir /app

WORKDIR /app

COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock

RUN apk add --update --virtual build-dependencies --no-cache $DEV_PACKAGES && \
    gem install bundler --no-document && \
    bundle config build.nokogiri --use-system-libraries && \
    bundle install --without development test heroku && \
    apk del build-dependencies

COPY package.json /app/package.json
COPY yarn.lock /app/yarn.lock

RUN yarn install --network-concurrency 1 && \
    yarn cache clean

COPY . /app

RUN yarn run build && \
    bundle exec rake assets:precompile DATABASE_URL=nulldb://localhost SECRET_KEY_BASE=secret_key_base

EXPOSE 3000
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

新しいコンテナと切り替え

フロントにALBを置いて、動的ポートマッピングを有効にしておくと新しいコンテナを起動して古いコンテナを停止するとダウンタイムなしで切り替えられる。

デフォルトの一時ポート範囲は 49153 〜 65535 で、この範囲は 1.6.0 より前の Docker バージョンで使用されます。Docker バージョン 1.6.0 以降では、Docker デーモンは /proc/sys/net/ipv4/ip_local_port_range の一時ポート範囲 (最新の Amazon ECS 最適化 AMI では 32768 ~ 61000) を読み取ろうとします。

となっているので最新のDockerを使う場合はセキュリティグループのALBからのインバウンドを32768-65535番を許可すれば良い。

タスク定義

  • 動的ポートマッピングを有効にさせるため、portMappingsにhostPortは設定しない
  • awslogs-groupに指定した名前のCloudWatch Logsのロググループを作成しておく必要がある

kptboard-prod.json
[
  {
    "image": "<account-id>.dkr.ecr.ap-northeast-1.amazonaws.com/ktbpard-prod",
    "name": "kptboard-prod",
    "memory": 512,
    "essential": true,
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "": "kptboard",
        "awslogs-region": "ap-northeast-1",
        "awslogs-stream-prefix": "kptboard"
      }
    },
    "portMappings": [
       {
         "containerPort": 3000
       }
    ],
    "environment": [
       {
         "name": "AWS_DEFAULT_REGION",
          "value": "ap-northeast-1"
       },
       {
         "name": "PARAMETER_STORE_PREFIX",
         "value": "kptboard.prod"
       }
    ]
  }
]

IAM ロール

AmazonEC2ContainerServiceforEC2RoleとSSMから取得できるポリシーを付ける

SSMから取得できるポリシー例

policy.json
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "ssm:GetParameters",
        "Effect": "Allow",
        "Resource": [
          "arn:aws:ssm:ap-northeast-1:<account-id>:parameter/kptboard.prod.*"
        ]
      },
      {
        "Action": "kms:Decrypt",
        "Effect": "Allow",
        "Resource": [
          "arn:aws:kms:us-east-1:<account-id>:key/alias/aws/ssm"
        ]
      }
    ]
}