70
72

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

UUUMAdvent Calendar 2019

Day 2

Rails × CircleCI × ECSのインフラ構築

Last updated at Posted at 2019-12-02

簡単なDocker RailsアプリをECSを利用して本番環境に上げるまでのまとめ

* あくまで参考に(実務でそのまま利用できるほどしっかり構築しておりません)

image.png

前提知識

ECSとは?クラスターとは?サービスとは?タスクとは?って人は
ECSの概念を理解しよう
などを読んでください。

Railsアプリ作成

まずはローカルでRailsアプリを作成しましょう。
機能は簡単なものでいいので、scaffoldなどを利用してサクッと作成してしまいましょう。
脳死で作成したい人は下記をご覧下さい。
Docker Rails Sampleアプリ構築 - Qiita

AWS上で利用するリソースの作成

コンソール上(or Terraformなど)からあらかじめ作成しておくべきものになります。

IAMロール・ポリシーの作成

ECSで運用するための必要なIAMロール・ポリシーを作成していきます。
ちなみにポリシーとは、ロールに付与される権限情報です。なのでポリシーのないロールは何も権限がない状態なのでまずはポリシーを作成してロールを作成していきましょう。

ポリシーの作成

作成手順

  1. IAMページに行って、サイドバーの「ポリシー」選択
  2. 「ポリシーの作成」ボタン押下
  3. JSONタブを開いて下記に記載したJSON内容をコピペして、「ポリシーの確認」押下
  4. それぞれのポリシー名を入力する

下記の4つのポリシーを作成する。

  1. AmazonSSMReadAccess
  2. AmazonECSTaskExecutionRolePolicy
  3. AmazonEC2ContainerServiceforEC2Role
  4. 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ページに行って、サイドバーの「ロール」→「ロールの作成」より下記のロールを作成する。
作成後、各ロールのページにて「ポリシーをアタッチする」を押下して上記で作成したポリシーを紐づける。

  1. ecsInstanceRole(→AmazonEC2ContainerServiceforEC2Roleに紐づける)
  2. AWSServiceRoleForECS(→AmazonECSServiceRolePolicyに紐づける)
  3. ecsTaskExecutionRole(→AmazonECSTaskExecutionRolePolicy,AmazonSSMReadAccessを紐づける)

参考
Amazon ECS タスク実行 IAM ロール

ALBの作成

ECSのサービス作成時にALBを登録しておけば、コンテナに動的にポートマッピングをしてくれるようになるので楽になります。

  1. Application Load Balancerを選択
  2. 名前を入力。サブネットを二つ選択。(ない場合は、適宜作成)
  3. セキュリティグループを選択。(ない場合は、適宜作成)
  4. ターゲットグループを選択or作成
  5. ターゲットグループにインスタンスを登録

クラスターの作成

ECSのサイドバーにある「クラスター」から「クラスターの作成」ボタンを押下
「クラスターテンプレートの選択」は「EC2 Linux + ネットワーキング」を選択

  1. クラスター名記載
  2. EC2インスタンスタイプの選択(お好み)
  3. キーペア(お好み。ただし、デバッグ時にSSHできた方がいいので設定しておくことをおすすめ)
  4. コンテナインスタンスの 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→データベース→[インスタンス名])
image.png

CircleCIの設定

.circleci/config.yml
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

image.png

環境変数名
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

rails-sample/ecs/production/app/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から取得するして設定するためのファイル

rails-sample/ecs/production/app/ecs-params.yml
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コンテナインスタンスの作成

Defaultクラスター作成しているし、IAMロールにecs:CreateClusterの権限付与されているから自動で作成なんかもしてくれるのかと思ったら作成してくれなかった。
なので、クラスター作成→インスタンス作成の方が良い(ちな、クラスター作成時にインスタンスも作成するようにはできるっぽい)
→カスタマイズされてるAMI利用時のみ初期スクリプトによってDefaultクラスターを作成しているのかもしれない

:hatched_chick: 参考 :hatched_chick:
Amazon ECS コンテナインスタンスの起動 - Amazon Elastic Container Service
Amazon ECS-optimized AMI - Amazon Elastic Container Service

インスタンスタイプについて

image.png

ある程度余裕持たないとタスク実行するための容量を持たなくて死ぬ
(ほんとは、ローカルや本番環境で動かした時の使用量見てタスク実行に必要なメモリを設定した方が良い)

ecs-cliでのタスク実行

  • ecs-params.yml ファイル内でtask_execution_roleを指定すること
  • task_execution_roleで指定した適切なポリシーを適用したIAM Roleを用意すること(エラーが出なくて、単純に実行されないので気づきにくい)

まとめ

ECSについてググればたくさん記事出てくるのですが、実際に活用しようとしてみるとたくさん落とし穴があります。もし利用しようか考えている人は一度デモアプリで利用してみることをお勧めします。

最後に

UUUMではインフラに詳しいエンジニアを欲しています。
詳しくはこちら →→→→→→ UUUM攻殻機動隊の紹介

70
72
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
70
72

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?