LoginSignup
0

More than 3 years have passed since last update.

第6回 AWSに自動でテスト/デプロイしてくれるインフラの設定・構築(総集編)

Last updated at Posted at 2021-01-09

本シリーズ集

タイトル
0 目標・やりたいこと
1 AWS編
2 rails開発環境構築編
3 Nginx・MySQL編
4 Capistrano編
5 CircleCI編
6 総集編

本記事について

 第1回~第5回まで全てやってきた結果の『ファイルの内容』・『ディレクトリ構造』(・今後の課題) を、本記事にまとめて、本シリーズ集は終了とする。

APサーバ

ディレクトリ構造

/sample
├ .circleci
|  └ config.yml
├ Dockerfile
├ Dockerfile_pro
├ docker-compose.yml
├ docker-compose_pro.yml
└ ~ $ rails new / その他gem(bundle)固有のコマンドで生成されたファイル群 ~

CircleCI関連

.circleci/config.yml
.circleci/config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/ruby:2.7.1-node-browsers
    steps:
      - checkout
      - restore_cache:
          key: Sample_Cache_{{ checksum "Gemfile.lock" }}
      - run:
          name: install Gemfile
          command: bundle install --path vendor/bundle
      - save_cache:
          key: Sample_Cache_{{ checksum "Gemfile.lock" }}
          paths:
            - ./vendor/bundle
  test:
    docker:
      - image: circleci/ruby:2.7.1-node-browsers
        environment:
          DB_HOST: 127.0.0.1
          DB_PASSWORD: root
      - image: circleci/mysql:5.7
        environment:
          MYSQL_USER: root
          MYSQL_ROOT_PASSWORD: root
    steps:
      - checkout
      - restore_cache:
          key: Sample_Cache_{{ checksum "Gemfile.lock" }}
      - run:
          name: if do not execute this, an error occurs.
          command: yarn install --check-files
      - run:
          name: Wait for DB
          command: dockerize -wait tcp://127.0.0.1:3306 -timeout 120s
      - run:
          name: setup bundler target path
          command: bundle config --local path vendor/bundle
      - run:
          name: Set up DB
          command: |
            bundle exec rake db:create
            bundle exec rails db:migrate
      - run:
          name: implement test
          command: bundle exec rspec ./spec/
  deploy:
    docker:
      - image: circleci/ruby:2.7.1-node-browsers
    steps:
      - checkout
      - restore_cache:
          key: Sample_Cache_{{ checksum "Gemfile.lock" }}
      - run:
          name: setup bundler target path
          command: bundle config --local path vendor/bundle
      - run:
          name: deploy app by capistrano
          command: bundle exec cap production deploy
workflows:
  version: 2.1
  test-deploy:
    jobs:
      - build
      - test:
          requires:
            - build
      - deploy:
          requires:
            - test
          filters:
            branches:
              only: master

Database周り

config/database.yml
config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("DB_USER") { "root" } %>
  password: <%= ENV.fetch("DB_PASSWORD") { "password" } %>
  host: <%= ENV.fetch("DB_HOST") { "db" } %>

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test

production:
  <<: *default
  database: app_production
  username: root
  password: root
  host: xx.xx.xx.xx #(APサーバのIPアドレス(ドメイン))

Capistrano周り

config/deploy.rb
config/deploy.rb
lock "~> 3.14.1"
set :application, "sample"
set :repo_url, "git@github.com:{accout}/{repository}"
set :deploy_to, "/home/ec2-user/#{fetch :application}"
set :pty, true
set :keep_releases, 5
set :linked_files, %w[config/master-pro.key config/database-pro.yml]

config/deploy/production.rb
config/deploy/production.rb
set :ip, "xx.xx.xx.xx" # APサーバのIPアドレス

server "#{fetch :ip}",
  user: "ec2-user",
  roles: %w{app},
  ssh_options: {
    keys: %w(/path/to/key),
    auth_methods: %w(publickey)
  }

task :upload do
  shared_path = fetch :shared_path
  on roles(:app) do
    upload! "config/database.yml", "#{shared_path}/config/database-pro.yml" unless test "[ -f #{shared_path}/config/database-pro.yml"
    upload! "config/master.key", "#{shared_path}/config/master-pro.key" unless test "[ -f #{shared_path}/config/master-pro.key ]"
  end
end

task :deploy => :upload do
  release_path = fetch :release_path
  on roles(:app) do
    execute "cp #{release_path}/config/database-pro.yml #{release_path}/databsae.yml; rm #{release_path}/config/database-pro.yml"
    execute "cp #{release_path}/config/master-pro.key #{release_path}/master.key; rm #{release_path}/config/master-pro.key"

    execute "sudo service docker start"

    container = capture "docker container ls -a -q -f name=test-rails-container"
    if container.present?
      execute "docker stop test-rails-container"
      execute "docker rm test-rails-container"
    end
    image = capture "docker image ls -q test-rails-image"
    if image.present?
      execute "docker rmi test-rails-image"
    end

    execute "docker-compose -f #{release_path}/docker-compose_pro.yml up -d"
  end
end



Dockerfile_pro
Dockerfile_pro
FROM ruby:2.7.1
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get install -y \
    build-essential \
    nodejs
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y --no-install-recommends yarn
WORKDIR /sample
COPY Gemfile /sample/Gemfile
COPY Gemfile.lock /sample/Gemfile.lock
RUN bundle install --without test development
COPY . /sample

docker-compose_pro.yml
docker-compose_pro.yml
version: '3'
services: 
  web:
    build:
      context: .
      dockerfile: Dockerfile_pro
    image: test-rails-image
    container_name: test-rails-container
    command: bundle exec rails s -p 3000 -b '0.0.0.0' -e production
    ports:
      - 3000:3000

 以下二つは、コマンド一つでローカルからWEBサーバ、DBサーバを起動できるようにしたもの。

config/deploy/production_web.rb(追加)
{application}ディレクトリ内にnginx ディレクトリを入れて。
config/deploy/production_web.rb
set :ip, "xx.xx.xx.xx" # WEBサーバのIPアドレス

server "#{fetch :ip}",
  user: "ec2-user",
  roles: %w{web},
  ssh_options: {
    keys: %w(/path/to/key),
    auth_methods: %w(publickey)
  }

task :webtask do
  on roles(:web) do
    upload! "./nginx" "/home/ec2-user/nginx"
    execute "sudo service docker start"

    container = capture "docker container ls -a -q -f name=test-nginx-container"
    if !container.empty?
      execute "docker stop test-nginx-container"
      execute "docker rm test-nginx-container"
    end

    execute "docker run --name test-nginx-container -p 80:80 -d test-nginx"
  # あらかじめ 
  # $ docker build -t test-nginx .
  # というコマンドを打っており、『test-nginx』という名前のDockerイメージがある体の設定
  end
end

config/deploy/production_db.rb(追加)
config/deploy/production_db.rb
set :ip, "xx.xx.xx.xx" # DBサーバのIPアドレス

server "#{fetch :ip}",
  user: "ec2-user",
  roles: %w{db},
  ssh_options: {
    keys: %w(/path/to/key),
    auth_methods: %w(publickey)
  }

task :dbtask do
  on roles(:db) do

    execute "sudo service docker start"

    container = capture "docker container ls -a -q -f name=test-db-container"
    if !container.empty?
      execute "docker stop test-db-container"
      execute "docker rm test-db-container"
    end

    execute "docker run --name test-db-container -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7"
  end
end




※注意環境設定周り
 そのままproduction環境で始めると、エラーが起きてしまうので、config/environments/production.rbファイルを一部書き換える。
config/environments/production.rb
# config.consider_all_requests_local       = false
# => trueにする
config.consider_all_requests_local       = true

# config.assets.compile = false
# => trueにする
config.assets.compile = true

前者は、エラーが発生した際に、開発環境と同じエラーを表示してくれるようにするもの。後者が特に重要で、これをtrueにしておかないと、production環境では強制的にエラーとなってしまう。

Webサーバ

ディレクトリ構成

/nginx
├ public
|  ├ 404.html
|  ├ 422.html
|  └ 500.html
├ nginx.conf
└ Dockerfile

 public ディレクトリ配下は、rails newコマンドで生成されたものを移植してきたものである。

nginx.conf
nginx.conf
server {
  listen 80;
  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  root /public;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  location / {
    try_files $uri @app;
  }

  location @app {
    proxy_set_header HOST $http_host
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-HOST $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://xx.xx.xx.xx:3000; 
  }
}

Dockerfile
Dockerfile
FROM nginx:latest
RUN rm -f /etc/nginx/conf.d/*

COPY ./public /public
COPY nginx.conf /etc/nginx/conf.d/nginx.conf
CMD nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

DBサーバ

DBサーバはネット上のDockerイメージとコマンドのみ

各種コンテナを起動して、通信してみる

ローカル(コンテナ内)で、以下のコマンドを打ち、

$ bundle exec cap production deploy
$ bundle exec cap production_web webtask
$ bundle exec cap production_db dbtask

ブラウザから
http://xx.xx.xx.xx』とWebサーバにアクセスすると、、、
スクリーンショット 2020-11-26 20.24.21.png

できたー!(あんまり見た目はパッとしませんが笑)
 ※ 少々心配になったので、$ rails g scaffoldコマンドでモデル・ビュー・コントローラを生成してきちんとパスにアクセスしたら表示されることを確認しました。

まとめ

 CapistranoやCircleCI・Nginxなど、一から調べながらやったのもあって、このシリーズの記事を書くのに正味10日ぐらいかかりました。笑

 実際の開発現場では、セキュリティの強固性を保つために、WEB・AP・DBサーバ(特にAP・DB)へのアクセスを厳格化し、デプロイ時には、webサーバを踏み台とした、web->ap、web->db 等のアクセス手法が取られる(?)と予想できるので、そうした サーバを経由した 方法に対応するデプロイも覚えていきたいと思ってます。

 あと今回未実装の部分で、実際の現場でやることといえば、rails db:create(migrate) RAILS_ENV=production等のコマンドをどうCapistranoに組み込むかぐらい?かな?

 ちなみに、この記事を書いてて気付いたのですが、Nginxのpublicフォルダがなくてもエラーのページが開かれました。
 もしかしたら、nginxの設定ファイルのrootは関係なく、rails(puma)側のpublicフォルダを参照してるんだと思います。(軽く手を動かして調べました)。
 多分ですが、try_files $uri/index.html $uri @railsApp;の部分に依存しているのかな?

 try_files について ->  https://tsyama.hatenablog.com/entry/try_files_is_difficult

 

最後に

 なんだかんだあってここまで来れました!
 (各技術の基本的なものは他の方が既に上げているので)ブログみたいな記事になってしまいましたが、本シリーズをご一読してくださった方ありがとうございました!

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
0