2020年5月の個人的Rails CircleCI設定のベストプラクティス

割とテンプレ化されてきたのでそろそろまとめておく。


rails + ecs-cliだとこんな感じ。結構良い感じにworkflowが作れていると思う。

version: 2.1

orbs:
  slack: circleci/slack@3.3.0

executors:
  ci:
    working_directory: ~/workspace
    docker:
      - image: circleci/ruby:2.6.3
      - image: circleci/mysql:5.7
    environment:
      - RAILS_ENV: test
      - MYSQL_USER: root
      - MYSQL_ALLOW_EMPTY_PASSWORD: true
      - TEST_DATABASE_HOST: 127.0.0.1
      - TEST_DATABASE_PORT: 3306
      - TEST_DATABASE_USERNAME: root
      - TEST_DATABASE_NAME: circle_test
  cd:
    working_directory: /root/project
    environment:
      - DOCKER_REPO_ROOT: <ecr-repo-url>
      - BASH: true
    docker:
      - image: docker/compose:1.18.0

commands:
  persist-workspace:
    steps:
      - persist_to_workspace:
          root: ~/workspace
          paths:
            - ./*
  attach-workspace:
    steps:
      - attach_workspace:
          at: ~/workspace
  bundle-install:
    steps:
      - restore_cache:
          key: bundle-cache-{{ checksum "Gemfile" }}
      - run:
          name: install gems
          command: bundle install --path vendor/bundle
      - save_cache:
          key: bundle-cache-{{ checksum "Gemfile" }}
          paths:
              - vendor/bundle
  wait-db:
    steps:
      - run:
          name: wait db
          command: dockerize -wait tcp://127.0.0.1:3306 -timeout 1m
  lint:
    steps:
      - run:
          name: rubocop
          command: bundle exec rubocop
  test:
    steps:
      - run:
          name: create db
          command: bin/rails db:drop db:create RAILS_ENV=test
      - run:
          name: ridgepole
          command: bundle exec ridgepole -c config/database.yml -f db/Schemafile -E test --apply --dump-with-default-fk-name
      - run:
          name: fixtures
          command: bundle exec rails db:fixtures:load RAILS_ENV=test
      - run:
          name: rails test
          command: bundle exec rspec
      - store_artifacts:
          path: coverage
  prepare-cd:
    steps:
      - setup_remote_docker:
          reusable: true
      - run:
          name: Install tools
          command: |
            apk --no-cache add --virtual .rundeps ca-certificates curl docker git python py-pip bash jq openssh
            pip install --upgrade awscli
            echo $RAILS_MASTER_KEY > config/master.key
            curl -o /usr/local/bin/ecs-cli https://s3.amazonaws.com/amazon-ecs-cli/ecs-cli-linux-amd64-latest
            chmod a+x /usr/local/bin/ecs-cli
  deploy-staging:
    steps:
      - run:
          name: Deploy staging
          no_output_timeout: 10m
          command: ./.circleci/bin/deploy-staging
  deploy-production:
    steps:
      - run:
          name: Deploy production
          no_output_timeout: 10m
          command: ./.circleci/bin/deploy-production
jobs:
  setup:
    executor:
      name: ci
    steps:
      - checkout
      - bundle-install
      - persist-workspace
  lint:
    executor:
      name: ci
    steps:
      - attach-workspace
      - bundle-install
      - lint
  test:
    executor:
      name: ci
    steps:
      - attach-workspace
      - bundle-install
      - wait-db
      - test
  deploy-staging:
    executor:
      name: cd
    steps:
      - checkout
      - prepare-cd
      - deploy-staging
      - slack/notify:
          message: |
            projectのstagingをリリースしたよ
  deploy-production:
    executor:
      name: cd
    steps:
      - checkout
      - prepare-cd
      - deploy-production
      - slack/notify:
          message: |
            projectのproductionをリリースしたよ
workflows:
  build:
    jobs:
      - setup
      - lint:
          requires:
            - setup
      - test:
          requires:
            - setup
      - deploy-staging:
          requires:
            - lint
            - test
          filters:
            branches:
              only:
                - develop
      - deploy-production:
          requires:
            - lint
            - test
          filters:
            branches:
              only:
                - master
#!/usr/bin/env bash
project="project"
branch="master"
enviroment="production"
cluster="${project}-${enviroment}"
version="latest"
region="ap-northeast-1"
repo="<ecr url>"

ecs_deploy() {
  base_dir="./ecs/${enviroment}-cluster/${project}-${enviroment}-$1"
  /usr/local/bin/ecs-cli compose \
    --cluster ${cluster} \
    --file $base_dir/docker-compose.yml \
    --ecs-params $base_dir/ecs-params.yml \
    ${@:2} `parse_params_file $base_dir run-params`
}

parse_params_file(){
  if [ -f $1/$2 ]; then
    cat $1/$2 | sed -e 's/=/ /' -e 's/^/--/' | tr '\n' ' '
  fi
}

# docker push
docker build --file=./docker/build/Dockerfile \
    --rm=false \
    -t ${DOCKER_REPO_ROOT}/uuum-${project}/${project}-app/${branch}:${version} .
/usr/local/bin/ecs-cli push \
  --region ${region} \
  --cluster-config ${cluster} \
  ${DOCKER_REPO_ROOT}/uuum-${project}/${project}-app/${branch}:${version}

# ecs cluster
/usr/local/bin/ecs-cli configure --cluster ${cluster} \
  --default-launch-type FARGATE \
  --region ${region} \
  --config-name ${cluster}

# application service
ecs_deploy app service up

# sidekiq
ecs_deploy sidekiq service up

# db-migrate
ecs_deploy db-migrate up