Help us understand the problem. What is going on with this article?

ECS×CodePipeline×CircleCIで作るお手軽CI/CD🍎

パイプラインファーストやってますか?

@toricls さんの 至高のCI/CDパイプラインを実現する5つの約束 より。

 2019-12-14 15.06.02.png

 2019-12-14 15.06.18.png

  • 一発目のデプロイからパイプラインを通す
  • rails new してすぐデプロイする
  • 以降 push するとすぐに ステージング/QA/開発環境にデリバリされる

理想だし全てのプロジェクトでやりたいけど結構しんどいですよね?

なぜしんどいのか

configどう書くんだっけ

CircleCIのconfigとかセットアップはプロジェクト初期の1回だけだし、
更新するのもそんなに頻繁ではないので書き方を忘れる。覚えていない。

作るものが多い

みんなが見れる環境にデリバリするためには、必要となるインフラのスタックが多い。
AWSでコンテナのアプリケーションを立ち上げるとしたら、以下のようなリソースが必要になってくる。

  • VPC
  • サブネット
  • ルートテーブル
  • ALB
  • ECR
  • ECSクラスタ
  • ECSタスク定義
  • ECSサービス
  • EC2 / AutoScalingGroup
  • 各種IAMロール / セキュリティグループ
  • DB

ドメインでアクセスできるようにしたければSSL証明書やRoute53も必要になってくるし、
パイプラインを作るにはS3, CodePipeline, CodeBuildとかも作らないといけない。

これだけのリソースを適切にセットアップできるメンバーがプロジェクト立ち上げ期にいない場合、
自然とパイプラインのセットアップは後回しになりがち。

インフラ / SREチームの助けが必要になってくる。

みんなアプリケーション書きたい

開発の大半を占めるのはアプリケーションの実装。
地道な足元の整備はモチベーション上がらながち。

どうすればパイプラインファーストが実現できるか

ツールの力を借りましょう。
世の中のナレッジを借りましょう。

CloudFormationですよ。
configのサンプルですよ。

コードを置いていくので使ってください。
これを使えば知識が特になくても、パイプラインができあがります。
ビルドやデプロイの待ち時間を除けば作業に必要なのは10分くらいです。

たったそれだけで、パブリックなURLで自分たちのアプリケーションにアクセスできるようになります。

サンプル

  • 作るのはRailsのアプリケーション。
  • とりあえずルートのパスにアクセスできるようにする。
  • ECSを使ってコンテナでpumaを立ち上げる(Nginxはなし)。
  • 起動タイプはEC2。

必要となるCloudFormationのテンプレートと buildspec.yml, .circleci/config.yml => Blue-Pix/rezept/pipeline_first
空っぽのRailsアプリも置いておきます => Blue-Pix/my_awesome_app

構築手順と解説

AWS, Github, CircleCIのアカウントを事前にご準備ください。

1. キーペアの作成

今回は起動タイプをECとするのでマネジメントコンソールからキーペアを1つ作成しておきます。
これはCloudFormation化できない部分です。

 2019-12-14 16.42.58.png

2. アプリケーション環境の構築

CloudFormationのスタックを作成します。
テンプレートファイル app.cf.yml をアップロードしてください。
 2019-12-14 16.44.52.png

テンプレートは自分のアプリケーションに合うように適宜ちょっとだけ修正が必要です。
pumaの設定ファイルの位置とか、

app.cf.yml
Command:
  - bundle
  - exec 
  - puma 
  - -C 
  - config/puma/production.rb 
  - -p
  - !Ref ApplicationPort

コンテナの環境変数まわりとか。

app.cf.yml
Environment:
  - Name: RAILS_LOG_TO_STDOUT
    Value: 1
  - Name: DB_HOST
    Value: hoge
  - Name: DB_USERNAME
    Value: hoge
  - Name: DB_PASSWORD
    Value: hoge
  - Name: RAILS_ENV
    Value: production
  - Name: SECRET_KEY_BASE
    Value: hoge

RAILS_LOG_TO_STDOUT はログを CloudWatch に流すために必要です。
ここで設定していても puma で std_out_redirect になってると流れなくなるので注意。

このテンプレートでは、VPC, ALB, ECS関連のリソースをまとめて作成します。

 2019-12-14 16.46.47.png

パラメータがいろいろありますが、基本はデフォルト値そのままで良いと思います。
キーペアだけ、先ほど作成したものを選択してください。

あとは AppName にアプリを識別する名前を入力します。
Env で環境を選択します。
test/qa/staging/production が用意してありますが足りなかったらテンプレートの AllowedValues を編集してください。
作成するリソースは全て ${AppName}-${Env} というプレフィックスをつけて管理するのがMyルールです。
何かのリソースのフォーマットに引っかかるのでアンダースコアは含めることができません。

 2019-12-14 16.48.46.png

インスタンスタイプは t3.micro にしてあります。メモリは1GiBです。
ServiceDesiredCount は デフォルト1です。
新しくデプロイを走らせると一時的に2つのコンテナが載った状態になるので、
1タスクあたりのメモリは450に設定しています(結構しんどい)。
コンテナインスタンスのオートスケールは含まれていないので、
インスタンスサイズとタスク数に応じて、
1コンテナインスタンスで捌けるくらいによしなに調整してください。
メモリが足りないと2回目以降のデプロイに失敗します。

スタックを作成すると27個のリソースができます。

  • ALB
  • ALBターゲットグループ
  • ALB用のセキュリティグループ
  • VPC
  • サブネット(アプリケーション用2つ, ALB用2つ)
  • インターネットゲートウェイ
  • ルートテーブル
  • 各種ルート/サブネットの関連付け
  • EC2用のセキュリティグループ
  • EC2用のIAMロール
  • インスタンスプロファイル
  • ECR
  • ECSクラスター
  • ECSタスク定義
  • ECSサービス
  • ECSタスク用のIAMロール
  • CloudWatchロググループ

3. Dockerイメージのプッシュ

2のスタックの作成は実は自動的には終わりません。
ECSサービスがタスクを起動しようとしますが、
イメージがないためコンテナが起動できずずっとループします。

とりあえずスタックの作成を終わらせるため、手元でビルドしておいて、
ECRが作られた時点でイメージをプッシュする作業が必要です。

 2019-12-14 17.11.37.png

2019-12-14_17_11_48.png

これ何とかしたいんですが上手い方法ありませんかね。
最初はサービスの desiredCount を0にすればいいじゃんと思っていたんですが、
0でもCloudFormationが最初にタスクを起動しようとするんですよね...

4. 確認

タスクがRUNNING / HEALTHYになればスタックの作成は完了です。

2019-12-14_17_16_48.png

ALBのDNS名を調べてアクセスしましょう。

 2019-12-14 17.20.51.png

おめでとうございますデプロイ完了です🎉

5. CodePipelineの構築

パイプラインを構築します。

手始めにアプリのリポジトリに buidspec.yml を追加してください。

buildspec.yml
version: 0.2
env:
  variables:
    DOCKER_BUILDKIT: "1"
phases:
  install:
    runtime-versions:
      docker: 18
  pre_build:
    commands:
      - $(aws ecr get-login --region $AWS_REGION --no-include-email)
  build:
    commands:
      - docker build -t $ECR_REPO_NAME . 
      - docker tag $ECR_REPO_NAME $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME:latest # latest
      - docker tag $ECR_REPO_NAME $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION # commit hash
  post_build:
    commands:
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME:latest
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION
      - printf '[{"name":"%s","imageUri":"%s"}]' $ECR_REPO_NAME $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME:latest > imagedefinitions.json
artifacts:
  files:
    - imagedefinitions.json

latest と git のコミットハッシュをタグとしてつけます。

今度は pipeline.cf.yml を使ってスタックを作成してください。

 2019-12-14 17.24.52.png

2とアプリ名と環境名を合わせてください。

他に必要なパラメータは以下です。

  • Githubリポジトリ名
  • リポジトリの所有者(アカウント名)
  • デプロイするブランチ
  • トークン

トークンは Developer Settings から repo にチェックを入れた Personal access tokenを発行してください。

 2019-12-14 17.28.49.png

スタックの中身は

  • アーティファクトを出力するS3バケット
  • CodeBuild
  • CodePipeline
  • CodeBuildとCodePipelineそれぞれのサービスロール
  • CircleCI用のIAMユーザー

です。

パイプラインはソースをチェックアウトしてDockerビルド、ECSサービスの更新というシンプルなものです。
1つ変わってるところとしては、 PollForSourceChangesfalse にして、
自動でパイプラインが走らないようにしていることです。

パイプラインはCIが通ってからCircleCI側から明示的に実行します。
ネットで調べるとCircleCI上でDockerビルドをする例が多く出てきますが、
ジョブが詰まる & フリープランでは時間がもったいないため、CodeBuildに任せています。

 2019-12-14 17.43.55.png

パイプラインも完成。

6. CircleCIを設定する

プロジェクトの設定は省きます。
configをアプリのリポジトリにおきます。
中身はまあ適当に。ほぼ何もやってません。

.circleci/config.yml
version: 2.1
commands:
  install_awscli:
    steps:
      - run: |
          sudo apt-get install python-pip
          sudo pip install awscli
  configure_env:
    steps:
      - run: |
          echo "export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY" >> $BASH_ENV
          echo "export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_KEY" >> $BASH_ENV
          echo "export AWS_DEFAULT_REGION=ap-northeast-1" >> $BASH_ENV
          echo "export PIPELINE_NAME=$PIPELINE_NAME" >> $BASH_ENV
jobs:
  build:
    parallelism: 1
    docker:
      - image: circleci/ruby:2.6.5
        environment:
          RAILS_ENV: test
          DB_HOST: 127.0.0.1
          DB_USERNAME: 'root'
          DB_PASSWORD: ''
          TZ: Asia/Tokyo
          BUNDLER_VERSION: 2.0.2
      - image: circleci/mysql:5.7
        environment:
          TZ: Asia/Tokyo
    working_directory: ~/my_app
    steps:
      - checkout

      - restore_cache:
          keys:
            - dependencies-{{ checksum "Gemfile.lock" }}
            - dependencies-

      - run: bundle install --deployment --jobs=4 --path vendor/bundle

      - save_cache:
          key: dependencies-{{ checksum "Gemfile.lock" }}
          paths:
            - "./vendor/bundle"

      - run: bundle exec rake db:create
      - run: bundle exec rake db:migrate

  deploy:
    docker:
      - image: circleci/python:3
    steps:
      - install_awscli
      - configure_env
      - run: aws codepipeline start-pipeline-execution --name $PIPELINE_NAME

workflows:
  version: 2
  commit:
    jobs:
      - build
      - deploy:<img width="1145" alt=" 2019-12-14 18.11.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83183/1512092e-9981-a8e3-e25f-cf5e4928526a.png">

          filters:
            branches:
              only:
                - master
          requires:
            - build

最初は失敗します。

前述のCirclCI用のIAMユーザーにアクセスキーを発行して、
CircleCI側に環境変数を登録してください。

また、キックするパイプラインを特定するために、パイプライン名も指定します。
パイプライン名は ${AppName}-${Env}-pipeline になります。

 2019-12-14 17.48.33.png

本当は複数環境を走らせるために config.yml に分岐書いたりしないと
本番運用には耐えないんですが、ここではシンプルにしています。

RerunするとCIが通ります。

 2019-12-14 18.11.23.png

Pipelineが開始されています。

 2019-12-14 18.11.58.png

まとめ

これで完成です。

以降は、該当のブランチにpushされるたびにCircleCIが走り、
CIをパスするとCodePipelineがキックされ、
イメージのビルドとECSサービスの更新を行いアプリケーションが反映されます。

テストとか、アセットのコンパイルとかはさておき、
これでパイプラインファーストが楽に実現できます。

待ち時間の体感を含めると10分はちょっと盛りましたすいません😂

以上ですご査収ください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away