Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Deploy Rails apps in 2021

Deploy Rails apps in 2021

事業成長を加速させたエンジニアリングのウラ側
https://medpeer.connpass.com/event/211745/

Takumi Shotoku

May 28, 2021
Tweet

More Decks by Takumi Shotoku

Other Decks in Technology

Transcript

  1. Deploy Rails apps in 2021
    事業成長を加速させたエンジニアリングのウラ側 2021/05/28
    1

    View Slide

  2. 自己紹介
    • 名前: 正徳 巧
    • 会社: メドピア株式会社
    • 所属: CTO室SRE
    • GitHub: @sinsoku (画像右上)
    • Twitter: @sinsoku_listy (画像右下)
    よろしくお願いします。
    2

    View Slide

  3. 最近の業務
    • AWSインフラの改善
    • デプロイの改善
    • Railsアプリのコードの改善
    • セキュリティ関連
    3

    View Slide

  4. ソフトウェアのデリバリ
    https://blogs.vmware.com/management/2020/03/vi-admin-to-devops.html
    4

    View Slide

  5. LeanとDevOpsの科学
    • デプロイの頻度
    • 変更のリードタイム
    • MTTR(平均修復時間)
    • 変更失敗率
    5

    View Slide

  6. どのくらいのデプロイ頻度を目指す?
    6

    View Slide

  7. 7

    View Slide

  8. 8

    View Slide

  9. 9

    View Slide

  10. 10

    View Slide

  11. CircleCIのレポート
    20171
    • メインラインブランチの安定性
    • デプロイ時間
    • デプロイ頻度
    1 https://circleci.com/landing-pages/assets/2017-VelocityReport-
    Updated-070219_JA.pdf
    11

    View Slide

  12. デプロイ時間
    デプロイ時間は概ねコントロールされており、80.2%の組織が15
    分以内にデプロイを行っています。特にデプロイ時間の短い組織
    (95パーセンタイル) では2.7分、中央値は7.6分です。それ以降
    はロングテールの分布が続き、下位5パーセンタイルの組織のデ
    プロイ時間は30分です。
    https://circleci.com/landing-pages/assets/2017-VelocityReport-Updated-070219_JA.pdf
    12

    View Slide

  13. デプロイ頻度
    全組織の75%が、最も活発なプロジェクトにおいて週に12回以下
    の頻度でデプロイを行っていました。上位の組織 (95パーセンタ
    イル) では、メインラインブランチを週に32回デプロイしていま
    す。これは中央値の5倍以上、下位5パーセンタイルの約24倍で
    す。
    https://circleci.com/landing-pages/assets/2017-VelocityReport-Updated-070219_JA.pdf
    13

    View Slide

  14. 更にデプロイ頻度の多い会社の事例
    14

    View Slide

  15. GitHubにおける継続的デリバリー2
    2 https://speakerdeck.com/yuichielectric/how-github-builds-and-deploy-software
    15

    View Slide

  16. GitHub Flow3
    3 https://guides.github.com/introduction/flow/
    16

    View Slide

  17. GitHub Flow3
    Deploy
    With GitHub, you can deploy from a branch for final testing in
    production before merging to main.
    つまりmainブランチは常に安定版になっており、問題が起きたら
    mainブランチをデプロイすることでロールバックできる。
    3 https://guides.github.com/introduction/flow/
    17

    View Slide

  18. GitHubの週辺りのデプロイ数(2015年)
    https://speakerdeck.com/yuichielectric/how-github-builds-and-deploy-software
    18

    View Slide

  19. GitHubのデプロイの変化
    • 社員数の増加(588人 => 1248人)
    • デプロイロック、デプロイキューの混雑
    • リリーストレインの導入
    • 複数のPRを1つのデプロイにまとめる
    19

    View Slide

  20. GitHubの週辺りのデプロイ数(2019年)
    https://speakerdeck.com/yuichielectric/how-github-builds-and-deploy-software
    20

    View Slide

  21. デプロイまとめ
    上位の組織
    • 時間: 3分程度
    • 頻度: 週に30回程度
    すごい会社(GitHub)
    • 時間: ?
    • 頻度: 週に300回以上
    21

    View Slide

  22. 少ないデプロイでは競合に勝てない時代
    22

    View Slide

  23. デプロイ頻度を増やすために
    • デプロイの改善
    • 半年前のMedPeerのデプロイは15〜17分
    • デプロイ頻度は月20〜25回程度
    • カナリアデプロイ
    23

    View Slide

  24. MedPeerのブランチ戦略
    24

    View Slide

  25. デプロイパイプライン
    GitHub
    1. リリースPRのマージ
    CodePipeline
    1. CodeBuildで
    !
    のビルド
    2. CodeBuildで db:migrate
    3. デプロイ
    25

    View Slide

  26. デプロイパイプライン
    GitHub
    1. Actionsで
    !
    のビルド4
    2. リリースPRのマージ
    CodePipeline
    1. CodeBuildで db:migrate
    2. デプロイ
    4 ビルドの高速化についてはテックブログ参照。https://tech.medpeer.co.jp/entry/2021/05/28/180408
    26

    View Slide

  27. リリースPRの自動作成
    • git-pr-release を利用5
    • 毎日10:30, 14:30に実行
    • 手動で実行も可能
    5 https://github.com/x-motemen/git-pr-release
    27

    View Slide

  28. Web上から実行できる
    28

    View Slide

  29. デプロイ頻度の計測
    $ git log --merges \
    --first-parent \
    --since='2019-1-1' \
    --format='%cd' \
    --date='format:%Y-%m' origin/master \
    | uniq -c
    masterへのマージ == デプロイ回数
    29

    View Slide

  30. 30

    View Slide

  31. デプロイ頻度を増やすには


    デプロイの改善
    • 15〜17分
    "
    => 約7分
    #
    • 月20〜25回 => 月45〜50回
    • カナリアデプロイ
    • 死活監視
    31

    View Slide

  32. MedPeerのRails appを動かすAWS構成
    32

    View Slide

  33. AWS CodeDeploy
    ECSの線形デプロイ、カナリアデプロイをサポートしている。6
    • カナリア: 最初の増分で10%、残り90%を5分後にシフト
    • カナリア: 最初の増分で10%、残り90%を15分後にシフト
    • 線形デプロイ: トラフィックの10%を毎分シフト
    • 線形デプロイ: トラフィックの10%を3分ごとにシフト
    6 太字部分のカスタマイズが可能。
    33

    View Slide

  34. カナリアデプロイの概要
    34

    View Slide

  35. CodePipeline vs GitHub Actions
    35

    View Slide

  36. 36

    View Slide

  37. GitHub Actionsの利点
    • AWS for GitHub Actionsが便利
    • Railsエンジニアが触りやすい

    !
    YAML

    "
    HCL(Terraform)
    • 1つのサービス(GitHub)で完結すると楽
    37

    View Slide

  38. 新しい開発フローの設計
    1. リリースPRを作る
    2. デプロイ承認後、canary環境にデプロイして動作確認
    3. 問題がなければ5分後にproduction環境にもデプロイ
    • mainブランチに自動マージ(=mainは安定版)
    4. 問題が起きたらワークフローをキャンセル
    • CodeDeployでロールバック
    38

    View Slide

  39. 社内サービスで動作検証
    39

    View Slide

  40. 実際に作ったワークフロー
    40

    View Slide

  41. db:migrate の実行
    • db/migrate/* のdiffで要否を判定する
    • ecs:RunTask でECS上でワンショットのタスクを実行する
    • sinsoku/amazon-ecs-run-task-definition のactionを作成7
    7 https://github.com/sinsoku/amazon-ecs-run-task-definition
    41

    View Slide

  42. db:migrate の実行
    steps:
    - uses: actions/[email protected]
    with:
    fetch-depth: 2
    # db:migrate Λ͢Δඞཁ͕͋Δ͔൑அ͢ΔͨΊɺલճͷmasterϒϥϯνͱͷdiffΛऔಘ͢Δɻ
    - name: Diff files in db/migrate
    id: migrate
    run: |
    MIGRATE_SIZE=$(git diff --name-only @^ -- db/migrate | wc -l)
    echo "::set-output name=size::$MIGRATE_SIZE"
    # ్தུ
    - name: Run a task on Amazon ECS
    if: steps.migrate.outputs.size != '0'
    uses: sinsoku/[email protected]
    with:
    task-definition: ${{ steps.update-task-def-cmd.outputs.task-definition-arn }}
    container: ${{ env.ECS_CONTAINER }}
    command: '["bin/rails", "db:migrate"]'
    service: ${{ env.ECS_SERVICE }}
    cluster: ${{ env.ECS_CLUSTER }}
    42

    View Slide

  43. デプロイ(canary)
    steps:
    - name: Deploy to Amazon ECS
    id: code-deploy
    uses: aws-actions/[email protected]
    with:
    task-definition: ${{ steps.task-def-web.outputs.task-definition }}
    service: ${{ env.ECS_SERVICE }}
    cluster: ${{ env.ECS_CLUSTER }}
    codedeploy-appspec: appspec.yaml
    codedeploy-application: example-app
    codedeploy-deployment-group: example-app-dg
    wait-for-service-stability: false
    - name: Wait for the canary environment to deploy
    run: |
    names=$(aws deploy get-deployment --deployment-id ${{ steps.code-deploy.outputs.codedeploy-deployment-id }} \
    --query 'deploymentInfo.loadBalancerInfo.targetGroupPairInfoList[].targetGroups[].name' \
    --output text)
    arns=$(aws elbv2 describe-target-groups --names $names \
    --query 'TargetGroups[].TargetGroupArn' \
    --output text)
    for arn in $arns; do
    aws elbv2 wait target-in-service --target-group-arn $arn
    done
    43

    View Slide

  44. デプロイ(production)
    production:
    needs: canary
    runs-on: ubuntu-latest
    timeout-minutes: 15
    environment:
    name: production
    url: https://example.com
    concurrency: deployment
    steps:
    - name: Configure AWS credentials
    uses: aws-actions/[email protected]
    with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: ap-northeast-1
    - name: Wait a deployment
    run: aws deploy wait deployment-successful --deployment-id ${{ needs.canary.outputs.deployment-id }}
    44

    View Slide

  45. ロールバック
    rollback:
    needs: [canary, production]
    if: cancelled() && needs.canary.outputs.deployment-id
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
    - name: Stop a deployment
    run: aws deploy stop-deployment --deployment-id ${{ needs.canary.outputs.deployment-id }} --auto-rollback-enabled
    - name: Run a task on Amazon ECS
    if: needs.canary.outputs.migrate-size != '0'
    uses: sinsoku/[email protected]
    with:
    task-definition: ${{ needs.canary.outputs.task-definition-arn }}
    container: ${{ env.ECS_CONTAINER }}
    command: '["bin/rails", "db:rollback", "STEP=${{ needs.canary.outputs.migrate-size }}"]'
    service: ${{ env.ECS_SERVICE }}
    cluster: ${{ env.ECS_CLUSTER }}
    45

    View Slide

  46. リリースPRのマージ
    merge:
    needs: production
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
    - name: Merge a release PR
    env:
    GITHUB_TOKEN: ${{ secrets.MEDPEER_GITHUB_TOKEN }}
    run: gh pr merge ${{ github.event.number }} --merge --repo ${{ github.repository }}
    46

    View Slide

  47. デプロイ頻度を増やすには


    デプロイの改善
    • 15〜17分
    "
    => 約7分
    #
    • 月20〜25回 => 月45〜50回

    $
    カナリアデプロイ
    • 死活監視
    47

    View Slide

  48. 次はMedPeerに導入して、
    もっとデプロイ頻度を増やすぞ!
    48

    View Slide

  49. この環境を体験したい人、一緒に作りたい人
    https://medpeer.co.jp/recruit/
    49

    View Slide