検証環境をbranch毎に作ってリリーススピードを改善した話

自己紹介

ロジクラでエンジニアをしている高梨です!

前回ロジクラのインフラ構成を紹介 したのに続き、今回もロジクラの開発を支えている環境周りに関して、リリーススピードを数倍にした例を紹介していきます!

以前ロジクラではチーム開発を進めていく中で、機能検証がスタックすることによって開発のスピードが停滞するという課題が発生していました。

具体的な問題としては、検証環境がstagingしか存在しておらず、複数の機能開発が同時に進んでいる場合に、PMなどに仕様を満たしているか確認してもらう順番待ちが発生したり、手直しがあった際に巻き戻しコストがかかりリリースが遅れるということがあります。

結果として、大きめの機能が入ってくるとリリース頻度が週に1度ほどになってしまうことがよくありました。

仕組みの概要

上記の問題を解消するため、変更した機能ごとに開発者以外が簡単に検証することができる stagingDev という環境を用意することをソリューションとしました。

branchごとにサブドメインの割り振りとECSのタスクを立ち上げ、今まで通りstagingのDBにアクセスできるようにして社内メンバーなら誰でも検証できるというものです。

処理の実行には github actoinsを利用しています。

以下に実際の設定を貼ります。

name: StagingDev作成君

on:
  pull_request:
    types: [opened, synchronize]
env: ...

jobs:
  build:
    if: contains(github.event.pull_request.labels.*.name, 'StagingDev')
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: kayac/ecspresso@v1
      with:
        version: latest
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with: ...
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
    - name: Setup Environment
      run: ... # 必要な環境変数の準備

    - name: Build Image
      run: |
        ./deploy/docker_build.sh staging ${{ secrets.IMAGE_REPOSITORY_URI }} $BRANCH_NAME ${{ secrets.SIDEKIQ_CREDENTIAL }} $BRANCH_NAME
        docker push ${{ secrets.IMAGE_REPOSITORY_URI }}:$BRANCH_NAME

    - name: Setup
      if: env.SETUP_COMPLETED == 'false'
      run: |
        # priorityが重複するとエラーになるので最大値を取得して重複しないようにする
        PRIORITY=$(aws elbv2 describe-rules --listener-arn ${arn} | jq -r '.Rules | map(select(.Priority == "default" | not) | .Priority | tonumber) | max + 1')
        # cloudformationでターゲットグループ, リスナールール, レコードセットの作成
        aws cloudformation create-stack \
          --stack-name $STACK_NAME \
          --template-body file://$(pwd)/deploy/cloudformation/staging_dev.yml \
          --parameters ParameterKey=BranchName,ParameterValue=$BRANCH_NAME ParameterKey=ListenerPriority,ParameterValue=$PRIORITY
        # 完了するまで待つ
        aws cloudformation wait stack-create-complete --stack-name $STACK_NAME

        TG_NAME=$STACK_NAME
        export TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --query "TargetGroups[?TargetGroupName == '$TG_NAME'] | [0]" | jq -r .TargetGroupArn)
        export BRANCH_NAME=$BRANCH_NAME
        export IMAGE_TAG=$BRANCH_NAME
        export ENTRY_POINT='"./deploy/init.sh"'
        # ECS Serviceの作成
        ecspresso create --config ./deploy/ecs/logikura-web-staging-dev/logikura-web-staging-dev.yml
        ecspresso create --config ./deploy/ecs/logikura-worker-staging-dev/logikura-worker-staging-dev.yml

    - name: Deploy
      if: env.SETUP_COMPLETED == 'true'
      run: |
        TG_NAME=$STACK_NAME
        export TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --query "TargetGroups[?TargetGroupName == '$TG_NAME'] | [0]" | jq -r .TargetGroupArn)
        export BRANCH_NAME=$BRANCH_NAME
        export IMAGE_TAG=$BRANCH_NAME
        export ENTRY_POINT='"./deploy/init.sh"'
        ecspresso deploy --config ./deploy/ecs/logikura-web-staging-dev/logikura-web-staging-dev.yml
        ecspresso deploy --config ./deploy/ecs/logikura-worker-staging-dev/logikura-worker-staging-dev.yml

    - ... # issueへのコメントやslack通知処理

上記の処理の流れを簡単に説明します!

  1. PRに StagingDev ラベルを貼り、そのPRに変更をプッシュすることによってActionがトリガ
  2. 該当ブランチで利用する環境変数をセット
  3. 該当ブランチのイメージを生成
  4. 用意しているcloudformationの設定を利用し、リソースを構築
    • Route53
    • LB (target groupの設定など)
  5. タスクのデプロイ
  6. 完了したらurlをPRにコメント

f:id:logikura:20211129120805p:plain

最後にslack通知を行います🚀

f:id:logikura:20211129120831p:plain

ちなみに設定は紹介しませんが、PRをクローズすると環境が消されるactionも用意しており、環境が作られっぱなしになることが内容になっています 👌

リソースの構築について

ざっくりとした環境構築までの流れは理解いただけたかなと思うので、ここからさらにstagingDev環境を具体的にどう構築しているかを記載します。

前回の記事でも紹介したようにロジクラはAWS上でECSを利用して運用されています。

実際に検証できる環境を作るためには、branchごとの変更がdeployされたECSの新しいタスクに対して独自のurlからアクセスできるようにリソースを作らないといけません。

CFでTGとRecordSetの作成

まず設定から記載します。

AWSTemplateFormatVersion: "2010-09-09"
Description: ""
Parameters:
  BranchName:
    Type: String
  VpcId:
    Type: String
    Default: 
  AlbArn:
    Type: String
    Default:
  Listener443Arn:
    Type: String
    Default: 
  ListenerPriority:
    Type: Number
Resources:
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: "/"
      Port: 80
      Protocol: "HTTP"
      HealthCheckPort: "traffic-port"
      HealthCheckProtocol: "HTTP"
      HealthCheckTimeoutSeconds: 5
      UnhealthyThresholdCount: 2
      TargetType: "ip"
      Matcher:
        HttpCode: "200"
      HealthyThresholdCount: 5
      VpcId:
        Fn::ImportValue:
          !Sub "${VPCStack}-VPC"
      Name: !Sub "logikura-tg-ecs-${Environment}"
      HealthCheckEnabled: true
      Name: !Sub "staging-dev-${BranchName}"
      Port: 443
      Protocol: HTTP
      TargetType: ip
      VpcId: !Ref VpcId
  ListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
      Conditions:
        - Field: host-header
          HostHeaderConfig:
            Values:
              - !Sub "dev-${BranchName}.com"
      ListenerArn: !Ref Listener443Arn
      Priority: !Ref ListenerPriority
  RecordSet:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneName: logikura.com.
      RecordSets:
      - Name: !Sub "dev-${BranchName}.com"
        Type: CNAME
        TTL: '60'
        ResourceRecords:
        - ***.elb.amazonaws.com

awscliで作っても良いのですが、削除が簡単にできるようにCFを採用しています。 terraformを利用している会社ではterraformで書き換えてもらってもよいかと思います!

最終的にbranch毎に割り振られたurlにアクセスすると、以下のようにbranch毎の変更が反映されたリソースにアクセスできます!

f:id:logikura:20211129120900p:plain

リソースの削除について

削除はCFで作ってるので簡単です。

githubactionsでPRが閉じた際にECSとELB等をまとめて消してます。

ecspresso delete --config ./deploy/ecs/logikura-web-staging-dev/logikura-web-staging-dev.yml --force
ecspresso delete --config ./deploy/ecs/logikura-worker-staging-dev/logikura-worker-staging-dev.yml --force

aws cloudformation delete-stack --stack-name $STACK_NAME
aws cloudformation wait stack-delete-complete --stack-name $STACK_NAME

一つ注意としてはPRの削除のたびに実行するとエラーになるので、stackが存在するかチェックする必要があります。

aws cloudformation describe-stacks --stack-name $STACK_NAME

注意事項

cloudformationのstackNameの制約で、 [a-zA-Z][-a-zA-Z0-9] にbranch名を限定しないとリソースの作成が失敗してしまうという問題があります。

branch名のルールが決まっていれば問題ないのであえて解決していませんが、もし命名に異なるルールがあるチームの場合は注意してください!

まとめ

ブランチごとに開発者以外が動作検証できる環境を作ったことで、それぞれの環境で仕様確認が同時並行で行うことができ、リリースがスタックすることがなくなりました 🙌

結果的に開発からユーザーに利用してもらうまでのスピードが数倍早くなり、顧客への提供スピードが上がっただけではなく、誰でも自由に検証できる環境があることで事前の社内周知やガイド作成など会社全体とし業務スピードをあげることにも成功しました!

今後も開発がスケールする仕組みを作っていき、さらに顧客へ価値を届けることができるようにしていきたいです 👍

最後に

強引にシェル芸で解決してる部分もあるのですが、もっとスマートにしていきたいので、 ロジクラでは一緒に開発してくれるメンバーを募集しています!

興味がある方はぜひ こちら をご覧ください!