Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

KubernetesのCronJobからJobを気軽に実行できるGitHub Actionsの仕組み

研究開発部 Architectグループ ML PlatformチームのKAZYこと新井です。

名古屋にある中部支店に所属しています。

KubernetesのCronJobからJobの作成と実行をGitHub Actionsでできるようにした話を紹介します。

なお、本記事は【R&D DevOps通信】という連載記事のひとつです。

目次

研究開発部とEKSの背景

研究開発部ではアプリケーション基盤としてEKS(Circuitと呼んでいます)を導入し、アプリケーション開発者への利用を推進中です。

buildersbox.corp-sansan.com

推進活動の一環として、Kubernetesの経験が浅い方でも気軽にはじめられるような取り組みをしています。

buildersbox.corp-sansan.com

こういった取り組みの甲斐もあり、研究開発部の方々が続々とバッチ処理やAPIサーバを作成してくれるようになったのでした。

CronJobの手動実行が必要になった経緯

Batch processing dies

定期バッチ処理*1は失敗や失敗や失敗やアルゴリズムの改修等で手動での再実行がしたくなるものです。

アプリケーション開発者自身で再実行が出来るようにしたいなと思うものの、基盤の整備状況などの観点から、これまでは基盤を運用しているArchitectグループのエンジニアがやっていました*2

しかし、Circuitに乗せるアプリケーションが多くなったのでその運用にも限界が来ました。

そのため簡単に定期バッチの手動実行できる仕組みを作ることにしました。

GitHub ActionsからCronJobの手動実行ができる仕組み

CronJobをJobとしてGitHub Actionsから実行できるようにしました。

kubectlのバージョンなどの実行環境をユーザに気にさせなくて済むためです*3

CronJobからJobを作成する方法

kubectlコマンドにあるCronJobからJobを作成するコマンドを使います。

kubectl create job --from=cronjob/<CronJob名> <Job名> -n <名前空間>

Kubectl Reference Docs

Jobマニフェストを作成してapplyすることもできますが、

  • 簡潔にワンライナーで表現できること
  • 1回実行したら不要になることが多いこと

から上記の方法にしました。

実装の方針

  • 既存のCronJobからのみJobを作成できるようにする(任意のJob作成はNG)
  • なるべくシンプルにする(使い方がわからないという問い合わせを0にしたい)

出来上がった仕組みの構成図

人間がGitHub Actionsのworkflow dispatchを用いて、フォームに必要事項を記入します。 そして実行ボタンをポチっとすると、EKSにCronJobをもとにJobを実行するものとしました。

作成したワークフロー

EKSをGitHub Actionsから操作できるようにして、kubectlコマンドを叩くだけです。

必要事項は

  • 実行環境
  • 名前空間
  • CronJobの名前

です。

name: Execute Job Manually

on:
  workflow_dispatch:
    inputs:
      environment:
        type: choice
        description: Environment
        options:
          - development
          - staging

      namespace:
        type: choice
        description: Namespace
        options:
          - namespace1
          - namespace2

      cronjob_name:
        description: CronJob Name

jobs:
  create-manifest:
    name: "[${{ github.event.inputs.environment }}] Execute Job Manually."
    runs-on: ubuntu-latest
    environment: ${{ github.event.inputs.environment }}
    permissions:
      id-token: write
    timeout-minutes: 5

    steps:

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ vars.AWS_ASSUME_ROLE_ARN }}
          aws-region: ap-northeast-1

      - name: Create kubeconfig file
        run: aws eks update-kubeconfig --name ${{ vars.CLUSTER_NAME }}

      - name: Install kubectl
        run: |
          curl -LO https://storage.googleapis.com/kubernetes-release/release/${{ vars.K8S_VERSION }}/bin/linux/amd64/kubectl
          chmod +x ./kubectl
          ./kubectl version
      

      # やりたいのはコレ
      - name: Create Job from CronJob
        run: |
          kubectl create job --from=cronjob/${{ github.event.inputs.cronjob_name }} ${{ github.event.inputs.cronjob_name }}-by-gha-$(date +'%Y%m%d%H%M%S') -n ${{ github.event.inputs.namespace }}

GitHub Actionsからkubectlを使うためのAWS側の設定

  • OIDCプロバイダを用いた認証
  • EKSへの権限付与

を行いました。

OIDCプロバイダを用いた認証はGitHub ActionsからAWSリソースへアクセスする方法と同様です。

Configuring OpenID Connect in Amazon Web Services - GitHub Docs

本番環境ではmainブランチからのみIAM Roleの使用権限を与えました*4。任意のブランチからワークフローの実行ができると、ワークフローを書き換えて、任意のJobをレビュー無しで作成可能になってしまうのためです*5

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::123456123456:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:main" # mainに限定している
                },
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}

IAM RoleへはEKSにアクセスできるよう以下のようなポリシーを付与しました。CircuitではBlue/Greenデプロイによるクラスター更新*6を行うため、一部ワイルドカードを利用して、アップデート後のクラスター名との差分を吸収できるようにしました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": "eks:DescribeCluster",
            "Resource": "arn:aws:eks:ap-northeast-1:123456789012:cluster/<EKS クラスター名>"
        }
    ]
}

これでEKS自体にはアクセスできるようになります。

GitHub ActionsからJobを作成するためのKubernetes側の設定

クラスターに対する IAM プリンシパルのアクセスの有効化 - Amazon EKSを参考にして、IAM Roleにクラスター内のリソースを操作する権限を与えてます。

IAM Role(sample-role)をgithub-actionsというユーザ名でgithub-actionsというグループに紐付けます*7

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: arn:aws:iam::123456789012:role/sample-role
      username: github-actions
      groups:
      - github-actions

CronJobの読み込みと、Jobの作成ができるRoleを作成します*8

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: create-job-from-cronjob
rules:
  - apiGroups:
      - "batch"
    resources:
      - cronjobs
    verbs:
      - get

  - apiGroups:
      - "batch"
    resources:
      - jobs
    verbs:
      - create

IAM Rroleと紐付けたグループ(github-actions)とClusterRoleを紐付けます。

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: github-actions
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: create-job-from-cronjob
subjects:
  - kind: Group
    name: github-actions
    apiGroup: rbac.authorization.k8s.io

これでCronJobからJobの作成ができるIAM Roleになりました。

注意したい点

ワークフロー実行が可能な人

どんなユーザがワークフローを実行できるかは一度確認すると良いでしょう。

ちなみにワークフロー実行者の履歴は残ります。

Job名の文字数

あとJob名は64文字未満の制限があるのでご注意ください。

おわりに

アプリケーション開発者自身で再実行できるようになったため、アプリケーション開発者と基盤開発者双方が幸せになりましたとさ。

めでたしめでたし。

求人

私の所属するML Platformチームを含む、研究開発部Architectグループでは一緒に働く仲間を募集しています。

R&D MLOps/DevOpsエンジニア / Sansan株式会社

*1:ここではCronJobリソースを指します。

*2:kubectlコマンドをゴニョゴニョと叩いていました。

*3:kubectlのバージョンは、クラスターのマイナーバージョンとの差分が1つ以内でなければならないとドキュメントには記載されています。 https://kubernetes.io/ja/docs/tasks/tools/install-kubectl/

*4:参考 https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services

*5:mainブランチへのコミットはレビューを必須にしています。

*6:新しいクラスタを作成して更新を行う方法です。

*7:グループ名とユーザ名は同じである必要はありません。

*8:名前空間を超えて使いたかったのでClusterRoleにしました。

© Sansan, Inc.