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

【サーバーサイド一式】Docker + Rails + Circle CI + Terraformでインフラをコードで環境構築 & ECSへ自動コンテナデプロイ【前半】

MENTAで教えてるので良かったら!

流行りの技術を使いたい我々は

おはようございます。今年もアドヴェントな感情が舞い散る季節になってきました。皆さん如何お過ごしでしょうか? 本番環境と仲良くやっていますでしょうか? 自分は非常に犬猿な仲になりつつあります。いいんです、人生はいつだって本番なのですから(?)

さて、巷のエンジニア達によるエンジニアの為のエンジニア論では、もはや

  • (主にサーバーサイドの)アプリをDockerを使って環境構築して
  • terraformでインフラの構成をコード化して
  • (Kubernetesなどのコンテナオーケストレーションツールを用いて)なるべく開発環境との差異が無いままコンテナデプロイをして
  • CircleCIでテストからデプロイまで自動化

といった事がサーバーサイドエンジニアとしては求められている、いや最早知らないようではやっていくのは難しい、といったご意見が結構な頻度で見られたりします。

(そういったご意見の是非はまた別の機会に預けるとして)だとしたら初心者のサーバーサイド志望のエンジニアさん達の為には、これらの

  • Docker
  • CircleCI
  • Terraform
  • コンテナデプロイ(今回はECS)

といった要素をバーーーーーーーーとチュートリアル形式で学んでいくのが手っ取り早いのでは無いか(そして自分の理解の整理にもなる)と思い立ちました! どうでしょうか?
つまりこの記事で現時点でナウいサーバーサイドの技術が一通り触れちゃう!ということです!
自分はその膨大さに今若干後悔しています。

まあ、じゃ、書きます!まーた多分長いよ。。。

※過去の長い記事たち
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】
【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】

明日(12/5)ワンマンライブなんでよかったらきてください
MV
予約はこちら

目次

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定
Terraformを使うための準備
前半はここで終わり

この記事を読むにあたっての前提

  • gitとgithubを使った事がある
  • Dockerをなんとなく理解している
  • Railsも触ったことある(触ったことなくてもできるかも)
  • AWSのコンソールでEC2やVPCを立てた事がある

もちろん、それ以外の方も是非是非挑戦してみてください。理解なんて後から帳尻合わせればいいんです。プログラミングは体で覚えるものです(?)

できるようになること

railsアプリをDockerで構築できて、pushしたタイミングでCircleCIが走って、予めTerraformで構築しておいたAWS環境(ECS)に自動デプロイする事ができるようになる!

注意してほしい事

  • DBはsqliteです。RDSとつないでません。
  • Webサーバもpumaで兼用してます。nginxなどのWebサーバーとつないでません =>これらはまた次回やろうと思います!

自分の環境

  • Ruby 2.6.3
  • Rails 6.0.1
  • Docker 19.03.4
  • Git 2.14.1
  • Terraform 0.12.8

全体像を把握しよう

目次
全体像を把握しよう←今ココ
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定
Terraformを使うための準備
前半はここで終わり

様々なツールやサービスを繋げていく事は、全体像の把握がとても大事になってきます。
まず大きい流れを確認しましょう。時系列順に列挙すると以下となります。

アプリが自動でコンテナデプロイされるまでの流れ

(0.TerraformでAWS上のリソースを定義)
1.RailsアプリをGithubにpush
2.CircleCIでCI開始
3.CIでDockerイメージをビルド
4.DockerイメージをECRへpush
5.ECSのTaskDefinitionを更新
6.CIでmigration
7.アプリがデプロイされる!

画像に表すと、以下となります。

image.png

ざっくり各ツールの役割も解説すると

terraform

インフラストラクチャ定義ツール。
クラウド上のリソースを定義ファイルの状態になるように生成・操作してくれる。
画面上でポチポチやってたインフラの操作をコードにできる。

rails

デプロイしたいアプリそのもの。

github

バージョン管理ツール。
アプリとCircleCIをつなぐ為のハブみたいな役割も担っている。

CircleCI

ビルド/テスト/デプロイなどについて自動実行できるサービス。
これを用いて色々面倒くさい事を自動化しよう。

Docker

アプリを動かす為の環境が一式詰まった仮想化プラットホーム。
ここでrailsが動く事はだいたい担保されてるので、Railsアプリが入ったDockerコンテナをそのまま本番環境にのっけたい

ECR

コンテナイメージをprivateな環境に格納しておけるサービス。
Railsアプリの環境を入れたDockerコンテナイメージをここにぶっ込みたい。

ECS

dockerコンテナを通して処理をしたりサービスを立ち上げたりと行った挙動を、EC2上で容易に行うためのAWSのサービス。EC2上での操作は全部AWSでやってくれるので、デプロイが簡単(のはずだがそうでもない)。

となります!非常に盛りだくさんですね。
はい、いつも通り順番に解説していきますのでご安心ください。

ではまずはDockerを用いて、Railsアプリを動かせる環境を作っていきましょう!

Dockerを用いてRailsの環境を構築

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築←今ココ
CircleCIでCIの設定
Terraformを使うための準備
前半はここで終わり

とりあえずローカル環境で一度作ります。
terraformとrailsアプリを一つのディレクトリにまとめちゃいたいので、 terraform_ecs_deploy
というディレクトリを作っちゃいましょう(githubのリポジトリは別にするので注意してください!)

ターミナル
$ mkdir terraform_ecs_deploy
$ cd terraform_ecs_deploy
$ rails new terraform_ecs_app --skip-javascript # webpackerは今回いらないです
$ cd terraform_ecs_app
$ rails db:migrate

さて、このタイミングでRailsアプリをDocker化します。
Dockerfileを作り、公式のRubyのイメージを取得し、以下のように書きましょう。
(今回簡単なDockerfileしか用意していないのはご了承ください。)

Dockerのインストールはこちら

Dockerfile
# 公式のイメージから取得
FROM ruby:2.6.3

# Dockerfile内部で使える変数として定義
ARG RAILS_ENV
ARG RAILS_MASTER_KEY

# コンテナ内のルートとする変数を/appと定義
ENV APP_ROOT /app

# 環境変数化
ENV RAILS_ENV ${RAILS_ENV}
ENV RAILS_MASTER_KEY ${RAILS_MASTER_KEY}

# コンテナ内のルートとする。
WORKDIR $APP_ROOT

# ローカルのGemfile, Gemfile.lockをコンテナ内のルートへコピー
ADD Gemfile $APP_ROOT
ADD Gemfile.lock $APP_ROOT

# bundle install実行。
# (バージョンのエラーが出る為、一応bundler 2.0.2を指定)
RUN \
    gem install bundler:2.0.2 && \ 
    bundle install && \
    rm -rf ~/.gem

# バンドルインストールが終わってから他のファイルもコンテナ内へコピー
ADD . $APP_ROOT

# 本番環境の場合プロダクション
RUN if ["${RAILS_ENV}" = "production"]; then bundle exec rails assets:precompile; else export RAILS_ENV=development; fi

# ポート3000番を公開
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]

では、このDockerfileを元にDockerコンテナをbuildし、railsを立ち上げてみます。

ターミナル
$ docker build -t terraform_ecs_app:latest . # -tでタグを指定
$ docker run -it -p 3000:3000 terraform_ecs_app:latest # -it

docker run時のオプションは以下の通りです。

-i, --interactive・・・コンテナのSTDINにアタッチする
-t, --tty・・・疑似ターミナルを割り当てる

このようになっていたらOKです。

image.png

http://localhost:3000にアクセスするとこのようにおなじみの画面になっているはずです。

image.png

※注意! docker run -it -p 3000:3000 terraform_ecs_app:latestが通らない場合

もしここで

docker: you are not authorized to perform this operation: server returned 401.
See 'docker run --help'.

このようなエラーが出る場合は、ターミナルで以下のコマンドを打ち、環境変数を設定してください。

ターミナル
export DOCKER_CONTENT_TRUST=0

これにより、Dockerイメージが改ざんされていないか、という整合性判定をスキップでき、上のコマンドが通るようになるはずです。

Docker Content Trust(DCT)について

Railsの詳細設定

さて、今回Railsの話が本題では無いので、機能的には簡素なもので良いです。なので、
- 表示用のトップ画面
- ALB(Application Load Balancer)HealthCheck用に、jsonでstatus: okが帰ってくるURL

の二つだけ実装しましょう。(もちろんALBとヘルスチェックについても後述します。)

ターミナル
$ rails g controller top index 
$ rails g controller health_check

topコントローラ はアクションとビューが自動生成されているはずなので、health_checkコントローラ だけ変更します。

app/controllers/health_check_controller.rb
class HealthCheckController < ApplicationController
  # ALBにステータスokを返す為のアクション
  def index
    render json: '{ "status": "ok" }'
  end
end

ルーティングも変更します。

config/routes.rb
Rails.application.routes.draw do
  root to: 'top#index'
  resources :health_check, only: [:index]
end

それぞれ以下のように表示されればOKです。

http://localhost:3000
image.png

http://localhost:3000/health_check
image.png

また、本番環境で tmp/pidstmp/socketsが作成されない事がある為、念のため.gitignoreに以下の記述を追加しましょう。

.gitignore
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
/db/*.sqlite3-*

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# ===============追加================
!/tmp/pids
!/tmp/sockets
# =============ここまで===============
# Ignore uploaded files in development.
/storage/*
!/storage/.keep

/public/assets
.byebug_history

# Ignore master key for decrypting credentials and more.
/config/master.key

最低限の機能が実装できたので、githubにpushしましょう。

githubにrailsアプリをpush

今回はgitとgihubの解説は省略します。

gitのインストール
githubの使い方

カレントディレクトリがrailsアプリなのを確認してから、以下のコマンドを打ってください。
(自分は今回リポジトリ名は terraform_ecs_app としています。)

ターミナル
$ git init
$ git commit -m "initial commit"
$ git remote add origin リポジトリのURL
$ git push origin master

githubのリポジトリが以下のようになっていたらOKです。

image.png

それではいよいよCircleCIのCI設定をおこなっていきましょう!

CircleCIでCIの設定

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定←今ココ
Terraformを使うための準備
前半はここで終わり

CircleCIとは、Saas型のCI/CDサービスであり、CI/CDでやることの基本であるビルドとテストとデプロイの3点が自動化可能なサービスです。

今回はこちらを使ってtestとデプロイ(ECRへのpush)の自動化を行なっていきたいと思います。

以下、CircleCIならではの特徴や料金についてはこちらをご参照ください。
いまさらだけどCircleCIに入門したので分かりやすくまとめてみた

CircleCIの導入

image.png

↓こちらのURLの右上あたりにあるLoginをクリックし、Log in with Githubを押してログインしましょう。
すると、自動的にgithubと紐づくはずなので、CIの設定画面まで行きましょう。
CircleCI

image.png

こちらの画像のように、Add Projectsを押すと、自分のgithubのリポジトリが出てくるので、
その中から先ほど作成したterraform_ecs_appを選択します。Set Up Projectを押しましょう。

スクリーンショット_2019-12-04_10_39_28.png

すると、プロジェクトの設定画面になると思います。
Operating SystemLinux
LanguageRubyのままで大丈夫です。

そして、そのすぐ下に英語でチュートリアルが書いてあります。和訳すると

  1. .circleciという名前のフォルダーを作成し、config.ymlファイルを追加します(ファイルパスが.circleci/config.ymlになるように)。
  2. config.ymlsample.ymlの内容を入力します(以下を参照)
  3. sample.ymlを更新して、プロジェクトの構成を反映します。
  4. この変更をGitHubにプッシュします。
  5. 構築を始めましょう!これにより、CircleCIでプロジェクトが起動し、Webhookが作業の更新をリッスンします。

とのことです。実際にそのすぐ下に、あらかじめCircleCIが用意してくれてたrails用の設定ファイルsample.ymlがあります。
指示通り、これをコピペして、ローカル環境で.circleciというフォルダを作り、その中にconfig.ymlという名前のファイルを作り、そこに貼り付ければOKです。

ただし、これだとrubyのバージョンが違うのと、テストフレームワークがrspecになっているので、今回はrailsデフォルトのテストフレームワークであるminitestに変えた以下のものにしてください。

.circleci/config.yml
# Ruby CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
#
version: 2
# jobsの中にタスクを定義。一番下のworkflowのjobsのなかで定義したタスクを使う。
jobs:
  # buildという名前のタスク定義
  build:
    docker:
      # specify the version you desire here
      - image: circleci/ruby:2.6.3-node-browsers


      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/repo

    # 実際の処理内容
    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-
      # runのたびに実行
      - run:
          name: install dependencies
          command: |
            gem install bundler -v 2.0.2
            bundle install --jobs=4 --retry=3 --path vendor/bundle

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

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # run tests
      - run:
          name: run tests
          command: |
            DISABLE_SPRING=true bundle exec rails test


      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

ここまでできたら、先ほどのCircleCIの設定画面に戻り、5の右側にあるStartbuildingを押してください。CircleCIが動き出します。

スクリーンショット_2019-12-04_11_04_15.png

こうすることによって次回以降はpushされたタイミングでCircleCIがJobし始めます!

(ちなみにroutes.rbを変えたせいでtestが失敗してしまうので、該当のtestをコメントアウトするか、正しい記述(pathをroot_pathに変えます)に直してからpushとbuildをしてください)

image.png

成功したらcircleCIのJOBSのタブの表示がこのようになります。(一番上の緑色がそうです。)

image.png

さて、ここまできたらようやくTerraformに入ります!!

Terraformを使うための準備

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定
Terraformを使うための準備←今ココ
前半はここで終わり

Terraformのインストール

Terraform公式
Terraformを用いることで、今まで手続き的だったインフラの工程をコード化することできます。

homebrewでもインストールできますが、Terraformのバージョンマネージャであるtfenvを使うのがおすすめです。バージョンアップに追従しやすいです。
まずはtfenv自体をインストールしましょう。

ターミナル
$ brew install tfenv
$ tfenv --version
tfenv 1.0.1

list-remoteコマンドでインストール可能なTerraformのバージョンを確認できます。
ここでは、0.12.8をインストールしましょう。

ターミナル
$ tfenv list-remote
.
.
0.12.8
$ tfenv install 0.12.8
$ terraform -v
Terraform v0.12.8

TerraformでAWSを扱うためのIAMユーザの設定

terraformでAWSを扱うにはIAMユーザのACCESS KEY、SECRET KEY(とDEFAULT REGION)が必要です。

ここだけはterraformの管理下におけないので、AWSのコンソールでIAMユーザを作成しし、アクセスキーとシークレットキーを発行してください。

IAMユーザの作成

発行できたら、ターミナルで以下のようにアクセスキーとシークレットキーを環境変数に設定してください。

ターミナル
 $ export AWS_ACCESS_KEY_ID=AKIxxxxxxxxx
 $ export AWS_SECRET_ACCESS_KEY=wJalxxxxxxxxxxxxxxxx
 $ export AWS_DEFAULT_REGION=ap-northeast-1

(PCの電源落としたり、ターミナル落としたりしたら消えてしまうのでもう一度入力し直してください。)

今回自分はAdministratorAccess ポリシーをアタッチしたIAMユーザのアクセスキーを用いていますが、こちら相当に強力な権限なので扱いには注意してください。
(間違ってもGitHubなどで公開してはダメです!)
AdministratorAccessポリシー以外では、権限不足で Terraform の実行が失敗することがあるので、その場合はエラーメッセージを参考に、必要な権限を付与しましょう。

これでTerraformを扱う準備ができました!

terraform用のgithubのリポジトリの作成

アプリ開発やCIとインフラ構成は別の話なので、リポジトリを分けるのがセオリーです。
今回はterraform_ecsという名前でgithubのリポジトリを作成します。

秘匿情報を扱うので、プライベートリポジトリにすることに注意してください。

image.png

さて、terraformのディレクトリ構造に関する考えは色々あるのですが
https://dev.classmethod.jp/devops/directory-layout-bestpractice-in-terraform/
https://qiita.com/anfangd/items/1b84f69fa2a4f8a29fbc
https://future-architect.github.io/articles/20190903/

今回は簡単のため環境だけディレクトリを分けて、その中に全てのtfファイルを突っ込む方式でいこうと思います。

terraform_ecs_deploy
├──terraform_ecs_app
└──terraform_ecs
    ├──prod
    └──stg

また、今回はステージング環境の構築は行いません!ほぼ同じことをやるだけなので、一度productionで構築できたらすぐできちゃうと思います。

では早速、terraform用のディレクトリを作成しましょう。

ターミナル
$ cd .. # railsアプリにいた場合
$ pwd
/Users/matsumotokazuki/Desktop/terraform_ecs_deploy # terraform_ecs_deployにいることを確認。
$ mkdir terraform_ecs
$ mkdir terraform_ecs/prod
$ cd terraform_ecs/prod

このようなディレクトリ構成になっていれば大丈夫です。

terraform_ecs_deploy
├──terraform_ecs_app
└──terraform_ecs
    └──prod # カレントディレクトリ

ここで、githubにpushする前に、terraformのインフラ構成ファイルを表すterraform.tfstateや秘匿情報を表すterraform.tfvarsなどをgitの管理下から除外します。(こちらも後述します)

ターミナル
$touch .gitignore
terraform_ecs/prod/.gitignore
/.terraform/*
/terraform.tfvars
/terraform.tfstate

それではpushしましょう。

ターミナル
$ cd .. # terraform_ecsディレクトリに移動
$ git init
$ git add -A
$ git commit -m "initial commit"
$ git remote add origin リポジトリ名
$ git push origin master

image.png

前半はここで終わり

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定
Terraformを使うための準備
前半はここで終わり←今ココ

諸々の準備が終わった時点で一旦区切ります!
後半からいよいよTerraformを使ってインフラを構築していきますので、気長にお待ちください。

後半はコチラ(にする予定)

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