簡単なDocker RailsアプリをECSを利用して本番環境に上げるまでのまとめ
* あくまで参考に(実務でそのまま利用できるほどしっかり構築しておりません)
前提知識
ECSとは?クラスターとは?サービスとは?タスクとは?って人は
ECSの概念を理解しよう
などを読んでください。
Railsアプリ作成
まずはローカルでRailsアプリを作成しましょう。
機能は簡単なものでいいので、scaffoldなどを利用してサクッと作成してしまいましょう。
脳死で作成したい人は下記をご覧下さい。
Docker Rails Sampleアプリ構築 - Qiita
AWS上で利用するリソースの作成
コンソール上(or Terraformなど)からあらかじめ作成しておくべきものになります。
IAMロール・ポリシーの作成
ECSで運用するための必要なIAMロール・ポリシーを作成していきます。
ちなみにポリシーとは、ロールに付与される権限情報です。なのでポリシーのないロールは何も権限がない状態なのでまずはポリシーを作成してロールを作成していきましょう。
ポリシーの作成
作成手順
- IAMページに行って、サイドバーの「ポリシー」選択
- 「ポリシーの作成」ボタン押下
- JSONタブを開いて下記に記載したJSON内容をコピペして、「ポリシーの確認」押下
- それぞれのポリシー名を入力する
下記の4つのポリシーを作成する。
- AmazonSSMReadAccess
- AmazonECSTaskExecutionRolePolicy
- AmazonEC2ContainerServiceforEC2Role
- AmazonECSServiceRolePolicy
AmazonSSMReadAccess
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:GetParameters",
"secretsmanager:GetSecretValue",
"kms:Decrypt"
],
"Resource": "*"
}
]
}
AmazonECSTaskExecutionRolePolicy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
AmazonEC2ContainerServiceforEC2Role
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeTags",
"ecs:CreateCluster",
"ecs:DeregisterContainerInstance",
"ecs:DiscoverPollEndpoint",
"ecs:Poll",
"ecs:RegisterContainerInstance",
"ecs:StartTelemetrySession",
"ecs:UpdateContainerInstancesState",
"ecs:Submit*",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
AmazonECSServiceRolePolicy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECSTaskManagement",
"Effect": "Allow",
"Action": [
"ec2:AttachNetworkInterface",
"ec2:CreateNetworkInterface",
"ec2:CreateNetworkInterfacePermission",
"ec2:DeleteNetworkInterface",
"ec2:DeleteNetworkInterfacePermission",
"ec2:Describe*",
"ec2:DetachNetworkInterface",
"elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
"elasticloadbalancing:DeregisterTargets",
"elasticloadbalancing:Describe*",
"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
"elasticloadbalancing:RegisterTargets",
"route53:ChangeResourceRecordSets",
"route53:CreateHealthCheck",
"route53:DeleteHealthCheck",
"route53:Get*",
"route53:List*",
"route53:UpdateHealthCheck",
"servicediscovery:DeregisterInstance",
"servicediscovery:Get*",
"servicediscovery:List*",
"servicediscovery:RegisterInstance",
"servicediscovery:UpdateInstanceCustomHealthStatus"
],
"Resource": "*"
},
{
"Sid": "ECSTagging",
"Effect": "Allow",
"Action": [
"ec2:CreateTags"
],
"Resource": "arn:aws:ec2:*:*:network-interface/*"
},
{
"Sid": "CWLogGroupManagement",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:DescribeLogGroups",
"logs:PutRetentionPolicy"
],
"Resource": "arn:aws:logs:*:*:log-group:/aws/ecs/*"
},
{
"Sid": "CWLogStreamManagement",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:DescribeLogStreams",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:log-group:/aws/ecs/*:log-stream:*"
}
]
}
ロールの作成
IAMページに行って、サイドバーの「ロール」→「ロールの作成」より下記のロールを作成する。
作成後、各ロールのページにて「ポリシーをアタッチする」を押下して上記で作成したポリシーを紐づける。
- ecsInstanceRole(→AmazonEC2ContainerServiceforEC2Roleに紐づける)
- AWSServiceRoleForECS(→AmazonECSServiceRolePolicyに紐づける)
- ecsTaskExecutionRole(→AmazonECSTaskExecutionRolePolicy,AmazonSSMReadAccessを紐づける)
ALBの作成
ECSのサービス作成時にALBを登録しておけば、コンテナに動的にポートマッピングをしてくれるようになるので楽になります。
- Application Load Balancerを選択
- 名前を入力。サブネットを二つ選択。(ない場合は、適宜作成)
- セキュリティグループを選択。(ない場合は、適宜作成)
- ターゲットグループを選択or作成
- ターゲットグループにインスタンスを登録
クラスターの作成
ECSのサイドバーにある「クラスター」から「クラスターの作成」ボタンを押下
「クラスターテンプレートの選択」は「EC2 Linux + ネットワーキング」を選択
- クラスター名記載
- EC2インスタンスタイプの選択(お好み)
- キーペア(お好み。ただし、デバッグ時にSSHできた方がいいので設定しておくことをおすすめ)
- コンテナインスタンスの IAM ロールに「ecsInstanceRole」を選択
RDSの作成
aws-cliでのRDS作成。
コンソール上からでもOKです。
aws rds create-db-instance \
--db-instance-identifier rails-sample-db-production \
--db-instance-class db.t2.micro \
--db-subnet-group-name rails-sample-db-subnet-group \
--engine mysql \
--engine-version 5.7.26 \
--allocated-storage 20 \
--master-username [username] \
--master-user-password [password] \
--backup-retention-period 3 \
参考
AWS CLI を使って RDS を作成する (自分用メモ) - Qiita
AWS-CLI Amazon Aurora インスタンス作成 - Qiita
AWS Systems Managerの設定
AWS Systems Managerは、タスク実行時にコンテナに注入する秘匿情報(環境変数)の管理に使えるAWSサービスです。
初めての人は設定の仕方を含め、
ECSでごっつ簡単に機密情報を環境変数に展開できるようになりました!
を見れば大体分かると思います。
AWS Systems Managerの左側メニューから「パラメータストア」→「パラメータの作成」をクリック。パラメータの詳細画面が表示されるので、パラメータのキー名と値を入力します。タイプには「安全な文字列」を選択します。
パラメータのキー名と値一覧
キー名 | 値 |
---|---|
/production/database_username | [RDSに設定したusername] |
/production/database_password | [RDSに設定したpassword] |
/production/database_host | [RDSインスタンスのエンドポイント] |
RDSインスタンスのエンドポイント(RDS→データベース→[インスタンス名])
CircleCIの設定
version: 2.1
orbs:
aws-cli: circleci/aws-cli@0.1.13
executors:
builder:
docker:
- image: circleci/buildpack-deps
commands:
init:
steps:
- checkout
- aws-cli/install
- install_ecs-cli
- setup_remote_docker
install_ecs-cli:
steps:
- run:
name: Install ECS-CLI
command: |
sudo curl -o /usr/local/bin/ecs-cli https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-linux-amd64-latest
sudo chmod +x /usr/local/bin/ecs-cli
jobs:
build:
executor: builder
steps:
- init
- run:
name: Build application Docker image
command: |
docker build -f build.Dockerfile --rm=false -t rails-sample-app-build:latest .
- run:
name: Save image
command: |
mkdir -p /tmp/docker
docker save rails-sample-app-build:latest -o /tmp/docker/image
- persist_to_workspace:
root: /tmp/docker
paths:
- image
deploy:
executor: builder
steps:
- init
- attach_workspace:
at: /tmp/docker
- run: docker load -i /tmp/docker/image
- run:
name: Assets precompile and Push Docker image
command: |
docker build -f assets.Dockerfile --build-arg RAILS_MASTER_KEY=${RAILS_MASTER_KEY} --rm=false -t rails-sample-app-build:latest .
- run:
name: Push Docker image
command: |
ecs-cli push rails-sample-app-build:latest
- run:
name: ECS Config
command: |
ecs-cli configure \
--cluster rails-sample-${CIRCLE_BRANCH} \
--region ${AWS_DEFAULT_REGION} \
--config-name rails-sample-${CIRCLE_BRANCH}
- run:
name: migrate deploy
command: |
ecs-cli compose \
--file ecs/${CIRCLE_BRANCH}/migrate/docker-compose.yml \
--ecs-params ecs/${CIRCLE_BRANCH}/migrate/ecs-params.yml \
--project-name rails-sample-${CIRCLE_BRANCH}-migrate \
up \
--launch-type EC2 \
--create-log-groups \
--cluster-config rails-sample-${CIRCLE_BRANCH}
- run:
name: Unicorn + Nginx deploy
command: |
ecs-cli compose \
--file ecs/${CIRCLE_BRANCH}/app/docker-compose.yml \
--ecs-params ecs/${CIRCLE_BRANCH}/app/ecs-params.yml \
--project-name rails-sample-${CIRCLE_BRANCH}-app \
service up \
--container-name nginx \
--container-port 80 \
--target-group-arn ${TARGET_GROUP_ARN} \
--timeout 0 \
--launch-type EC2 \
--create-log-groups \
--cluster-config rails-sample-${CIRCLE_BRANCH}
workflows:
version: 2
build-deploy:
jobs:
- build
- deploy:
requires:
- build
filters:
branches:
only:
- master
CircleCIに設定する環境変数
CircleCIのプロジェクトの設定ページ(Settings→[アカウント名or組織名]→[プロジェクト名])に行き、下記の画像の箇所から設定する
https://circleci.com/gh/[アカウント名or組織名]/[プロジェクト名]/edit#env-vars
環境変数名 | 値 |
---|---|
AWS_ACCESS_KEY_ID | [AWSのアクセスキーID] |
AWS_ACCOUNT_ID | [AWSのアカウントID] |
AWS_DEFAULT_REGION | [AWSのデフォルトリージョン] |
AWS_ECR_REPOSITORY_URL | [AWSのECRリポジトリURL] |
AWS_SECRET_ACCESS_KEY | [AWSのシークレットアクセスキー] |
RAILS_MASTER_KEY | [config/master.keyの値] |
TARGET_GROUP_ARN | [ターゲットグループのarn] |
Task definitionの作成
docker-compose.yml
version: "3"
services:
app:
image: [ECRのリポジトリURI]
entrypoint: bundle exec unicorn -c config/unicorn.rb
env_file:
- ../env
working_dir: /projects/rails-sample
logging:
driver: "awslogs"
options:
awslogs-region: "ap-northeast-1"
awslogs-group: "rails-sample-production/app"
awslogs-stream-prefix: "rails-sample-app"
nginx:
image: [ECRのリポジトリURI]
ports:
- 0:80
links:
- "app:app"
env_file:
- ../env
working_dir: /projects/rails-sample
logging:
driver: "awslogs"
options:
awslogs-region: "ap-northeast-1"
awslogs-group: "rails-sample-production/nginx"
awslogs-stream-prefix: "rails-sample-nginx"
* Nginxの設定ファイルは適宜用意してください。上記のnginxの欄にnginx設定ファイル群の設置・起動用のスクリプトentrypoint: /bin/bash /etc/nginx/start.sh
を用意するなど。
ecs-params.yml
タスク実行時に実行ロールの指定やコンテナに注入する環境変数をAWS Systems Managerから取得するして設定するためのファイル
version: 1
task_definition:
# タスク実行時のロールを指定
task_execution_role: ecsTaskExecutionRole
services:
# 起動するコンテナを記載(app, nginx)
app:
# 何らかの理由で失敗・停止した際に、タスクに含まれる他のすべてのコンテナを停止するかどうか(デフォルトはtrue)
essential: true
# AWS Systems Managerから秘匿情報を取得してコンテナに環境変数を注入
secrets:
- value_from: /production/database_username
name: DATABASE_USERNAME
- value_from: /production/database_password
name: DATABASE_PASSWORD
- value_from: /production/database_host
name: DATABASE_HOST
nginx:
essential: true
run_params:
network_configuration:
awsvpc_configuration:
assign_public_ip: ENABLED
コンテナ全体に注入する環境変数の設定
各環境(production, stagingなど)ごとのディレクトリ以下にenv
ファイルを用意してそこに記載する
# ここのファイルに追加した環境変数は全てのコンテナに展開されます
# Rails
APP_HOST=54.238.241.230
RAILS_ENV=production
RAILS_LOG_TO_STDOUT=1
RAILS_SERVE_STATIC_FILES=1
# RDS
DATABASE_NAME=rails-sample_production
DATABASE_PORT=3306
DATABASE_POOL=10
# Unicorn
UNICORN_PORT=23380
UNICORN_TIMEOUT=180
UNICORN_WORKER_PROSESSES=2
# Nginx専用
NGINX_APP_SERVER_NAME=app
NGINX_APP_SERVER_PORT=23380
NGINX_DOCUMENT_ROOT=/projects/rails-sample/public
NGINX_FRONT_SERVER_NAME=54.238.241.230
構築の際に詰まる可能性のあるポイント
ECSコンテナインスタンスの作成
- インスタンスへのIAMロールを付与すること
- ecs-agentのインストール ( Amazon ECS コンテナエージェントのインストール - Amazon Elastic Container Service )
- EC2インスタンスの
/etc/ecs/ecs.config
にCLUSTER_NAME=クラスター名
の登録 - 所属するクラスターを変更する場合、
/var/lib/ecs/data/ecs_agent_data.json
を削除してからecs-agentを再起動する
Defaultクラスター作成しているし、IAMロールにecs:CreateClusterの権限付与されているから自動で作成なんかもしてくれるのかと思ったら作成してくれなかった。
なので、クラスター作成→インスタンス作成の方が良い(ちな、クラスター作成時にインスタンスも作成するようにはできるっぽい)
→カスタマイズされてるAMI利用時のみ初期スクリプトによってDefaultクラスターを作成しているのかもしれない
参考
Amazon ECS コンテナインスタンスの起動 - Amazon Elastic Container Service
Amazon ECS-optimized AMI - Amazon Elastic Container Service
インスタンスタイプについて
ある程度余裕持たないとタスク実行するための容量を持たなくて死ぬ
(ほんとは、ローカルや本番環境で動かした時の使用量見てタスク実行に必要なメモリを設定した方が良い)
ecs-cliでのタスク実行
-
ecs-params.yml
ファイル内でtask_execution_role
を指定すること -
task_execution_role
で指定した適切なポリシーを適用したIAM Roleを用意すること(エラーが出なくて、単純に実行されないので気づきにくい)
まとめ
ECSについてググればたくさん記事出てくるのですが、実際に活用しようとしてみるとたくさん落とし穴があります。もし利用しようか考えている人は一度デモアプリで利用してみることをお勧めします。
最後に
UUUMではインフラに詳しいエンジニアを欲しています。
詳しくはこちら →→→→→→ UUUM攻殻機動隊の紹介