コネヒト開発者ブログ

コネヒト開発者ブログ

ECS Scheduled Taskの管理をecscheduleでGitOps化しました

こんにちは。コネヒトのテクノロジー推進グループでインフラエンジニアをしている laughk です。 今回は定期実行バッチで利用しているECS Schedule Taskの管理に Songmu/ecschedule を導入し、GitOps化した話をまとめます。

サマリ

  • ecscheduleを導入する前の定期実行バッチの管理状況と課題
  • 技術選定 - ecshceduleを選定した理由
  • 導入プロセス
  • GitOps化
  • 導入後どうなったか

ecscheduleを導入する前の定期実行バッチの管理状況と課題

コネヒトでは提供するサービスのWeb基盤にAmazon ECSをフル活用しており、定期実行バッチにおいてもECS Scheduled Taskを利用しています。ECS Scheduled TaskはECS Taskを cron のように定期実行でき、とても便利なものです。一方でその管理においては利用する ECS Task の定義、EventBridge のルールなどの複数のAWSリソースを組み合わせる必要があり、煩雑になりがちです。

ECS Scheduled Taskの導入に関しましては別途記事がありますので、興味がある方はそちらも参照いただければうれしいです。

tech.connehito.com

ecschedule導入以前もECS Scheduled Taskのインフラの一部をTerrafrormによって管理をしている状況ではありました。管理対象はバッチ実行に利用するTask Definitionのみで、1つのバッチに対し1つのTask Definitionを用意し、スケジュール部分は手動で設定を行うという運用です。

しかしながらこの方法では次のような問題がありました

  • 各バッチで利用するコンテナイメージは同一のものであり、違いは実行コマンドのみであるのにそれぞれ別のリソースとして定義する必要があった
  • Task Definitionの container_definitions は JSON を文字列で記載する必要があるため、似たようなコピペが量産されている状況だった
  • スケジュールはコメントアウトでメモ程度に書かれているだけで、実際に適用する際は手作業で行っていた

特にTask Definitionのコードは次のような形で、container_definitioncommand 以外ほぼ同じもののコピペがほとんどを占めている状況で、開発者にとってもわかりやすいものではありませんでした。

image

技術選定 - ecshceduleを選定した理由

この状況を打開する手段はいくつか考えられましたが最終的に次の理由でecscheduleを採用しました。

github.com

  • ECS Scheduled Taskの管理に特化しているので、余計な機能がなくシンプル
  • ECSクラスタ名を指定し、その中のTask Definitionを指定するという思想は今のコネヒトのECSクラスタの状況と相性がよかった
  • タスクの単発実行にも対応している ( ecschedule run -conf conf.yaml batchName1 みたいな実行)
  • containerOverrides も含め、一気通貫YAMLで書ける

ほかの手段として Ansible の AWSモジュール を利用することも検討しました。

docs.ansible.com

こちらでもかなり効率的にECS Scheduled Taskの管理が行えるとみていました。しかしながらcontainerOverrideはJSON文字列を渡す必要がある点や、社内でAnsibleがバリバリ使われている状況ではありませんでした。開発者も巻き込んで使えるツールとしてはシンプルさでecshceduleに劣ると判断し、今回は利用を見送りました。

導入プロセス

ecscheduleにはECSクラスタ名を指定して、既存のECS Scheduled Taskの状態をYAML形式で出力するdump機能があります。そのため使い始めること自体はとても簡単にできました。次のようにコマンドを実行するだけでOKでした。

$ ecschedule dump -cluster cluster1  --region ap-northeast-1 > cluster1/event-rules.yaml

ですが、コネヒトの場合は既存のECS Scheduled Taskを管理するだけではTask DefinitionのTerrafrorm管理からは解放されませんでした。そのためバッチを実行する専用のTask DefinitionをECSクラスタにつき1つ用意し、EventBridge RuleのContainerOverrideでコマンドを指定するように構成を変更しました。

before after
image image

コネヒトではdev環境(開発環境)とprd環境(本番環境)でそれぞれ3つのECSクラスタでECS Scheduled Taskを利用している状況で、最終的に次のようなファイル構成に落ち着きました。

.
|-- cluster1/
|   |-- dev-events-rules.yaml
|   |-- dev-task-definition.json
|   |-- prd-events-rules.yaml
|   `-- prd-task-definition.json
|-- cluster2/
|   |-- dev-events-rules.yaml
|   |-- dev-task-definition.json
|   |-- prd-events-rules.yaml
|   `-- prd-task-definition.json
`-- cluster3/
    |-- dev-events-rules.yaml
    |-- dev-task-definition-ec2.json
    |-- dev-task-definition-fargate.json
    |-- prd-events-rules.yaml
    |-- prd-task-definition-ec2.json
    `-- prd-task-definition-fargate.json

ecschedule向けのYAMLファイルを (クラスタ名)/(環境名)-event-rules.yamlとしています。一方で、バッチで利用するTask Definitionの管理はecscheduleではできません。そのため執筆時点ではaws cliで適用可能なJSONを(クラスタ名)/(環境名)-task-definition.jsonにおいています。一つだけEC2とFargateを併用しているクラスタがあるため、そちらはEC2向けとFargate向けで別々に用意しています。

これらのファイルをGitHubリポジトリに乗せ、PRベースでECS Scheduled Task上のバッチ追加、変更をコード管理できるようにしました。PRがApproveされ、マージされたらEventBridge Ruleについてはecscheduleのapplyを実行するだけで変更の適用のほとんどが完結するようになり、運用負荷を大幅に減らすことができました。1

GitHub ActionsでGitOps化

ここまででもGitHubリポジトリ上のYAMLでバッチの管理が可能になり、とても便利になりました。しかしながら、変更が発生した際に人の手でYAMLの内容を適用する必要があることに変わりはなく、どのタイミングで変更が適用したのかということ人が意識しておく必要がありました。

そこでecscheduleのYAMLを編集する際に発生する実行コマンドをMakefileを用意してTask化し、その内容をもとにGithub Actions経由で自動適用できるところまで対応しました。

Makefileは次のような内容です。

ENV=dev

.PHONY: diff-all
diff-all: diff-cluster1-api diff-cluster2 diff-cluster3

.PHONY: apply-all
apply-all: apply-cluster1-api apply-cluster2 apply-cluster3

.PHONY: diff-cluster1-api
diff-cluster1-api:
  ecschedule diff -conf ./cluster1-api/$(ENV)-events-rules.yaml -all

.PHONY: diff-cluster2
diff-cluster2:
  ecschedule diff -conf ./cluster2/$(ENV)-events-rules.yaml -all

.PHONY: diff-cluster3
diff-cluster3:
  ecschedule diff -conf ./cluster3/$(ENV)-events-rules.yaml -all

.PHONY: apply-cluster1-api
apply-cluster1-api:
  ecschedule apply -conf ./cluster1-api/$(ENV)-events-rules.yaml -all

.PHONY: apply-cluster2
apply-cluster2:
  ecschedule apply -conf ./cluster2/$(ENV)-events-rules.yaml -all

.PHONY: apply-cluster3
apply-cluster3:
  ecschedule apply -conf ./cluster3/$(ENV)-events-rules.yaml -all

このMakefileを利用することを前提に、次のような .github/workflow/apply.yaml を用意しました。

name: apply
on:
  push:
    branches:
      - main

jobs:
  apply:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: setup ecschedule
        env:
          ECSCHEDULE_VERSION: 0.3.1
          ECSCHEDULE_MD5_HASH:  28bdd174e4f012e0345d701ac46f1038
        run: |
          sudo apt-get install -y make
          curl -L https://github.com/Songmu/ecschedule/releases/download/v${ECSCHEDULE_VERSION}/ecschedule_v${ECSCHEDULE_VERSION}_linux_amd64.tar.gz -o ecschedule.tgz
          echo "${ECSCHEDULE_MD5_HASH}  ecschedule.tgz" | md5sum -c -
          tar vxf ecschedule.tgz
          sudo mv -v ecschedule_v${ECSCHEDULE_VERSION}_linux_amd64/ecschedule /usr/local/bin/.

      - name: apply (dev)
        env:
          AWS_ACCESS_KEY_ID:  ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          make apply-all ENV=dev

      - name: apply (prd)
        env:
          AWS_ACCESS_KEY_ID:  ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          make apply-all ENV=prd

AWSを利用するためのsecretsはあらかじめ登録しておくことが前提です。applyはmainブランチの内容は毎回適用しても問題ないだろうと判断しているため、細かい条件は指定せずすべて適用するようにしています。

最後にApproveなしのコードがmainブランチに入ることを防ぐため、リポジトリの「Require pull request reviews before merging」を有効にします。執筆時点ではインフラエンジニアのレビューを必須にするため「Require review from Code Owners」も有効にしています。

image

これで人の手を介さずコードの内容の適用が可能になり、バッチ追加・変更のほとんどはGitHubのPullRequestで完結するようになりました。

導入後どうなったか

ecscheduleによるGitOps化ができてからは、開発者がローカルにecscheduleの実行環境を持つ必要もなくなり、バッチの更新・追加についてはGitHubのPullRequest上のやり取りで完結するようになりました。ecscheduleの仕様上自動削除には対応できてはいませんが、それでもほとんどの運用がYAMLの編集のみで完結するようになりました。

image

また計画停止が発生する際にバッチごとに影響範囲をPullRquest上で確認しながらYAMLに落とし込むことができ、とても効率的かつ、チームの議論の過程を残しながら対応ができるようになりました。参考までに、最近実際にPullRequest上で確認しながら計画停止を行った様子を紹介します。

image

image

image

合意をとった状態で対象のブランチをローカルに持ってきてecscheduleのapplyコマンドを実行すれば、その通りに確実にバッチを停止させられるため確実にオペミスを避けることができ、とても安心して計画停止を実施できるようにもなりました。

さいごに

ecscheduleを導入しGitOpsの実現によってECS Scheduled Task上の定期実行バッチの運用負荷を下げた取り組みを紹介しました。

今回紹介した話は来月3/17(水)13:30~18:00に開催される「KGDC Tech Conference #0 通信インフラだけじゃないKDDIグループの多彩な技術」というオンラインイベントでもお話いたします。ぜひ興味ある方、「直接話を聞いてみたい!」という方はご参加いただけるとうれしいです。

kgdc.connpass.com

2021.03.17 追記

この話を 「KGDC Tech Conference #0 通信インフラだけじゃないKDDIグループの多彩な技術」でも発表しました。当日利用したスライドもあわせてみていただけると嬉しいです。

この記事はShodo (https://shodo.ink) で執筆されました。


  1. Task Definitionについては、latestタグのDockerイメージを利用している関係で更新はほとんど発生していません。