Goで実装するゼロインスタンスにスケールされた環境起動の自動化

Goで実装するゼロインスタンスにスケールされた環境起動の自動化

はじめに

こんにちは、EC基盤開発本部SRE部カート決済SREブロックの金田です。

普段はSREとしてZOZOTOWNのカート決済機能のリプレイスや運用を担当しています。本記事では夜間・休日にインスタンス数を0にスケールされた開発環境を、Slackから起動できるツール「sc8leon(スケールオン)」について紹介します。開発環境のゼロスケール運用で同様の課題を抱えている方の参考になれば幸いです。

目次

背景・課題

背景

ZOZOTOWNの開発環境は、コスト削減を目的として夜間・休日にKEDAを利用したゼロスケールを導入しています。EKS上で稼働するPodのレプリカ数を0にするとともに、関連するAWSリソース(主にAuroraCluster)も停止することでインフラコストを削減しています。

デイリーのコスト削減イメージ

ZOZOTOWNの開発環境におけるコスト最適化については、以下の資料でも詳しく紹介しています。併せてご覧ください。

speakerdeck.com

この夜間・休日の停止を検討した際、開発環境を主に使用する開発部署から以下のような懸念が挙がりました。

  • 日跨ぎに関連する処理のリリース前に開発環境での動作確認ができない
  • 夜間・休日に本番障害が発生した際、緊急リリースに向けた開発環境での検証ができない
  • 早朝から業務を開始する際や休日出勤時に開発環境が使えない

これらの懸念を解消することが夜間・休日停止の導入条件となったため、必要なときに誰でも環境を起動できるツールの開発が必要になりました。

課題

ZOZOTOWNのバックエンドは多数のマイクロサービスが協調して動作しています。各マイクロサービスを稼働させているEKS環境は「CIOpsからGitOpsへ。Flux2でマイクロサービスのデプロイを爆速にした話」で紹介しているようにFluxによるGitOpsを導入しており、Gitリポジトリの状態がクラスタに同期されます。そのため、kubectlコマンドによるオブジェクトの直接変更ができず、ゼロスケールの解除には以下の作業が必要でした。

  1. 各マイクロサービスのScaledObjectに設定されているMinReplicaを変更するマニフェスト修正
  2. Pull Requestの作成とレビュー依頼
  3. マージ後、Fluxによるクラスタへの同期を待機
  4. AuroraClusterはAWSコンソールから手動で起動

この手順には以下の課題がありました。

課題 詳細
運用負荷 深夜・休日にマニフェスト修正とPR作成を行う必要があり、PRのレビュー・承認も含めて最低2名の対応が必要になる
対象の多さ 多数のマイクロサービスを起動する必要があり、手動での対応は現実的ではなく、対応漏れのリスクもある
属人化 マニフェストの修正方法やAuroraClusterの起動手順を知っている人しか対応できない
戻し忘れのリスク 起動後にMinReplicaを元に戻すPRを忘れると、翌日以降もゼロスケールが解除されたままになる

これらの課題を解決するために、誰でもSlackから簡単に環境を起動でき、自動でリセットされるツール「sc8leon」を開発しました。

sc8leonの概要

sc8leonは、ゼロスケールされた開発環境をSlackから起動できるツールです。Slack Workflowから実行でき、環境の起動からリセットまでを自動化しています。

機能一覧

sc8leonは以下の5つの機能を提供しています。

機能名 説明 ユースケース
Start ゼロスケールされている環境の即時起動 深夜に障害が発生し、開発環境で修正確認を行いたい
Stop 起動中の環境を停止 動作確認が完了したので環境を停止する
Schedule Start 起動時間のリスケジュール 翌朝6時から業務を開始するため、5:30に起動時間を設定する
Schedule Stop 停止時間のリスケジュール 日跨ぎの動作確認を行うため、深夜2時まで停止時間を延長する
Status 起動状況、起動/停止スケジュールの可視化 現在の環境の状態やスケジュールを確認する

アーキテクチャ

sc8leonのアーキテクチャ

sc8leonは以下のコンポーネントで構成されています。

コンポーネント 役割
Slack Workflow ユーザーからの操作を受け付け、Lambdaを呼び出す
AWS Lambda sc8leonのメイン処理を実行
Amazon EventBridge Scheduler 起動/停止/リセットのスケジュール実行
AWS Secrets Manager GitHub Appsの認証情報を保存
GitHub Apps マニフェスト変更のPR作成・マージを自動化
Flux Gitリポジトリの変更をEKSクラスタに同期

実装

使用技術

sc8leonは以下の技術スタックで実装しています。

カテゴリ 技術
言語 Go
CLIフレームワーク urfave/cli
実行環境 AWS Lambda
スケジューラ Amazon EventBridge Scheduler
AWS SDK aws-sdk-go-v2
Git操作 go-git
GitHub API go-github
YAML操作 sigs.k8s.io/yaml

設定ファイル

背景で述べた「対象の多さ」という課題に対して、sc8leonでは操作対象となるマイクロサービスとAuroraClusterを設定ファイルで事前に定義しています。これにより、ユーザーは環境を指定するだけで、関連するすべてのリソースを一括で起動・停止できます。

設定ファイルは環境ごとに用意しており、go:embedを使用してバイナリに埋め込んでいます。そのため、対象の追加・変更時は再ビルドが必要です。

//go:embed config/*.yaml
var configFiles embed.FS

func LoadConfig(target string) (*TargetSetting, error) {
    data, err := configFiles.ReadFile(fmt.Sprintf("config/%s", target))
    if err != nil {
        return nil, err
    }
    var setting TargetSetting
    yaml.Unmarshal(data, &setting)
    return &setting, nil
}

設定ファイルの構造

設定ファイルにはservice(マイクロサービス)とrdscluster(AuroraCluster)の2種類のリソースを定義できます。

マイクロサービスの設定例

services:
  - type: service
    namespace: zozotown-api
    serviceName: zozotown-api
    manifest:
      scaledObject:
        path: path/to/scaledobject.yaml
        compactSeqIndent: true
      deployment:
        path: path/to/deployment.yaml
        timestampKeyName: last-canary-retry-timestamp
        compactSeqIndent: false
    dependencies:
      - type: rdscluster
        name: zozotown-rds-cluster
フィールド 説明
namespace Kubernetesのnamespace
serviceName サービス名
manifest.scaledObject.path ScaledObjectマニフェストのファイルパス
manifest.scaledObject.compactSeqIndent YAML出力時のリストインデント設定
manifest.deployment.path Deploymentマニフェストのファイルパス
manifest.deployment.timestampKeyName Flaggerの更新をトリガーするためのアノテーションキー名
dependencies 依存するリソース(起動順序の制御に使用)

ZOZOTOWNのEKS環境にはFlaggerも導入されています。timestampKeyNameは、Deploymentのアノテーションにタイムスタンプを付与してPodの再作成を促すために使用しています。マニフェストの変更だけではFlaggerによるカナリアデプロイがトリガーされない場合に利用します。

ZOZOTOWNでのFlagger導入については、以下の記事も参照してください。

techblog.zozo.com

AuroraClusterの設定例

services:
  # 同一AWSアカウント内のAuroraCluster
  - type: rdscluster
    clusterID: zozotown-rds-cluster
    startScheduleName: zozotown-aurora-start
    originStartTime: cron(30 6 ? * MON-FRI *)
    stopScheduleName: zozotown-aurora-stop
    originStopTime: cron(0 0 ? * TUE-SAT *)

  # 別AWSアカウントのAuroraCluster
  - type: rdscluster
    awsAccountID: "123456789012"
    role: sc8leon-access-role
    clusterID: other-account-cluster
    startScheduleName: other-account-cluster-aurora-start
    originStartTime: cron(0 7 ? * MON-FRI *)
    stopScheduleName: other-account-cluster-aurora-stop
    originStopTime: cron(0 0 ? * TUE-SAT *)
フィールド 説明
awsAccountID 別AWSアカウントの場合のアカウントID
role 別AWSアカウントの場合のAssumeRole用ロール名
clusterID AuroraClusterのクラスタID
startScheduleName 起動用EventBridge Schedulerのスケジュール名
originStartTime 元の起動時間(リセット時に復元する値)
stopScheduleName 停止用EventBridge Schedulerのスケジュール名
originStopTime 元の停止時間(リセット時に復元する値)

originStartTimeoriginStopTimeは、sc8leonのリセット処理でEventBridge Schedulerのスケジュールを復元する際に使用します。

依存関係の管理

マイクロサービスにはdependenciesを設定でき、起動時は依存先を先に起動し、停止時は依存元を先に停止します。これにより、DBに依存するマイクロサービスが起動した際にDBがまだ起動していないといった問題を防いでいます。

マイクロサービスの起動/停止

KEDAを使用したゼロスケールでは、ScaledObjectリソースのminReplicaCountを0に設定します。cron triggerで勤務時間中のみdesiredReplicasを1以上に指定し、勤務時間外はcron triggerが無効になります。これにより、minReplicaCountの設定に従ってPodのレプリカ数が0になります。

sc8leonでは、このminReplicaCountの値を変更することでマイクロサービスの起動/停止を制御しています。

  • 起動: minReplicaCountを1以上に変更(実際の実装ではKEDAのcron triggerに設定されたdesiredReplicasと同じ値に変更)
  • 停止: minReplicaCountを0に変更

FluxによるGitOpsを導入しているため、以下の流れでクラスタに反映されます。

  1. go-gitでリポジトリをクローンし、ブランチを作成
  2. マニフェストファイルを編集
  3. コミット・プッシュ
  4. GitHub Apps経由でPull Requestを作成
  5. CIの完了を待機後、Pull Requestをマージ
  6. Fluxがマージを検知し、クラスタに同期

Lambda環境でのGit操作

sc8leonでは、go-gitとgo-billyを使用してGitのストレージとファイルシステムの両方をインメモリで扱っています。

func NewGitClient(repositoryPath, repositoryName string, awscli *AWSClient) (*GitClient, error) {
    tokenSrc, _ := FetchToken(awscli)
    token, _ := tokenSrc.Token()

    // ファイルシステムをインメモリで作成
    fs := memfs.New()

    // Gitのストレージもインメモリで作成し、shallow cloneを実行
    r, err := git.Clone(memory.NewStorage(), fs, &git.CloneOptions{
        URL:           fmt.Sprintf("https://x-access-token:%s@github.com/st-tech/%s.git", token.AccessToken, repositoryName),
        ReferenceName: plumbing.ReferenceName("refs/heads/master"),
        SingleBranch:  true,
        Depth:         1,
    })
    return &GitClient{repository: r, FS: fs}, nil
}

ポイントは以下の3点です。

設定 説明
memory.NewStorage() Gitオブジェクト(コミット、ツリー等)をインメモリで管理
memfs.New() ワーキングツリー(ファイル)をインメモリで管理
SingleBranch: true, Depth: 1 shallow cloneにより、必要最小限のデータのみ取得

マニフェストの編集

マニフェストの編集では、YAMLをAST(抽象構文木)としてパースし、minReplicaCountの値を直接変更しています。

func EditMinReplicaCount(node *yaml.Node, replicaCount string) {
    updateMappingNode(node, []string{"spec"}, "minReplicaCount", replicaCount)
}

GitHub Apps認証

sc8leonではGitHub Appsを使用してGitHub APIを操作しています。GitHub Appsの認証情報(App ID、Installation ID、秘密鍵)はAWS Secrets Managerに保存し、Lambda関数から取得しています。

Lambda環境では、AWS Parameters and Secrets Lambda Extensionを使用してSecrets Managerから認証情報を取得しています。このExtensionはローカルHTTPエンドポイント(localhost:2773)を提供し、Secrets Managerへのアクセスをキャッシュします。これにより、APIコール数の削減とレイテンシの改善を実現しています。

func GetSecretValueWithLambdaExtension(secretName string) (string, error) {
    secretExtensionEP := fmt.Sprintf("http://localhost:2773/secretsmanager/get?secretId=%s", secretName)
    headers := map[string]string{
        "X-Aws-Parameters-Secrets-Token": os.Getenv("AWS_SESSION_TOKEN"),
    }
    client := &http.Client{}
    req, _ := http.NewRequest("GET", secretExtensionEP, nil)
    for key, value := range headers {
        req.Header.Set(key, value)
    }
    resp, _ := client.Do(req)
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    var secretValue GetSecretValueOutput
    json.Unmarshal(body, &secretValue)
    return secretValue.SecretString, nil
}

取得した認証情報を使用して、GitHub AppsのInstallation Tokenを生成します。このトークンはGit操作(clone、push)とGitHub API操作(PR作成、マージ)の両方で使用します。

func FetchToken(awscli *AWSClient) (oauth2.TokenSource, error) {
    appValues, _ := getGitHubAppSecret(awscli)

    appid, _ := strconv.ParseInt(appValues.AppID, 10, 64)
    appTokenSource, _ := githubauth.NewApplicationTokenSource(appid, []byte(appValues.PrivateKey))

    instid, _ := strconv.ParseInt(appValues.InstallationID, 10, 64)
    return githubauth.NewInstallationTokenSource(instid, appTokenSource), nil
}

Installation Tokenは有効期限が1時間であるため、処理完了後はRevokeTokenでトークンを無効化し、セキュリティリスクを軽減しています。

GitHub Appsによる自動マージ

課題で述べた「運用負荷」と「属人化」を解決するために、GitHub Appsを使用してPRの作成からマージまでを自動化しています。

通常、マニフェストを管理しているリポジトリのmasterブランチにはブランチ保護ルールが設定されており、PRのマージには最低1名のApproveが必要です。しかし、GitHub Appsが生成したトークンでは、同じGitHub AppsによるApproveができないという制約があります。

この制約を回避するために、ブランチ保護ルールの「Allow specified actors to bypass required pull requests」設定を有効にしています。この設定にsc8leon用のGitHub Appsを追加することで、sc8leonが作成したPRはApproveなしでマージが可能になります。

func (c *GitHubAPIClient) CreatePullRequest(owner, repo, base, head, title, body string) (*github.PullRequest, error) {
    pr, resp, err := c.client.PullRequests.Create(c.ctx, owner, repo, &github.NewPullRequest{
        Title: github.String(title),
        Body:  github.String(body),
        Base:  github.String(base),
        Head:  github.String(head),
    })
    return pr, nil
}

func (c *GitHubAPIClient) MergePullRequest(owner, repo string, number int) (*github.PullRequestMergeResult, error) {
    result, resp, err := c.client.PullRequests.Merge(c.ctx, owner, repo, number, "", &github.PullRequestOptions{})
    return result, nil
}

また、PRのマージ前にCIが完了するのを待機しています。これにより、CIでエラーが検出された場合はマージされません。

func (c *GitHubAPIClient) WaitMergeUntilWIPComplete(owner, repo, branchName string, timeout int) error {
    for {
        result, _, _ := c.client.Checks.ListCheckRunsForRef(c.ctx, owner, repo,
            fmt.Sprintf("heads/%s", branchName),
            &github.ListCheckRunsOptions{CheckName: github.String("WIP")})

        checkrun := result.CheckRuns[0]
        switch checkrun.GetStatus() {
        case "queued", "in_progress":
            time.Sleep(5 * time.Second)
        default:
            return nil
        }
    }
}

AuroraClusterの起動/停止

AuroraClusterの起動/停止は、AWS SDK for Go v2を使用してRDS APIを直接呼び出しています。

func (c *AWSClient) StartRDSCluster(setting *RDSClusterSetting) (string, error) {
    rdscli, ok := c.rds[setting.AWSAccountID]
    if !ok {
        rdscli = c.rds["default"]
    }
    out, err := rdscli.StartDBCluster(c.ctx, &rds.StartDBClusterInput{
        DBClusterIdentifier: aws.String(setting.ClusterID),
    })
    if err != nil {
        return "", err
    }
    return *out.DBCluster.Status, nil
}

複数のAWSアカウントにまたがるAuroraClusterを操作する必要があるため、設定ファイルにAWSアカウントIDとAssumeRole用のRole ARNを記載しています。これらの情報を使用して、アカウントごとにRDSクライアントを生成しています。

スケジュール機能

起動時間や停止時間のリスケジュールには、Amazon EventBridge Schedulerのone-time schedulesを使用しています。one-time schedulesは指定した時間に一度だけ実行され、ActionAfterCompletion: Deleteの設定により実行後に自動で削除されます。

func (c *AWSClient) CreateScheduleForLambda(scheduleName, scheduleExpression, lambdaFunction, payload string) error {
    _, err := c.scheduler["default"].CreateSchedule(c.ctx, &scheduler.CreateScheduleInput{
        FlexibleTimeWindow: &schedulertypes.FlexibleTimeWindow{
            Mode: schedulertypes.FlexibleTimeWindowModeOff,
        },
        Name:                       aws.String(scheduleName),
        ScheduleExpression:         aws.String(scheduleExpression),
        ScheduleExpressionTimezone: aws.String("Asia/Tokyo"),
        Target: &schedulertypes.Target{
            Arn:     aws.String(fmt.Sprintf("arn:aws:lambda:%s:%s:function:%s", ...)),
            RoleArn: aws.String(fmt.Sprintf("arn:aws:iam::%s:role/...", ...)),
            Input:   aws.String(payload),
        },
        ActionAfterCompletion: schedulertypes.ActionAfterCompletionDelete,
    })
    return err
}

Schedule Start(起動時間の前倒し)

早朝から環境を使いたい場合に、起動時間を前倒しで予約する機能です。指定された時間にStart処理を実行するone-time scheduleを登録します。

func scheduleStart(env Env, startTime *time.Time) (ScheduleStartResult, error) {
    // ...省略...
    scheduleName := "sc8leon-start"
    scheduleExpression := fmt.Sprintf("at(%s)", startTime.Format("2006-01-02T15:04:05"))
    payload := fmt.Sprintf(`{"env": "%s", "command": "start"}`, env)

    if err = awscli.CreateScheduleForLambda(scheduleName, scheduleExpression, "sc8leon", payload); err != nil {
        return ScheduleStartResult{}, errors.Join(err, errors.New("failed to create eventbridge schedule"))
    }

    // リセット処理のスケジュールも登録
    registerResult, err := RegisterResetFunc(env, awscli, *startTime)
    // ...省略...
}

Schedule Stop(停止時間の延期)

日跨ぎの動作確認など、通常の停止時間を超えて環境を使いたい場合に、停止時間を延期する機能です。Schedule Stopでは以下の3つのone-time scheduleを登録します。

スケジュール 実行タイミング 処理内容
停止延期処理 本来の停止時間(0:00)の30分前 マイクロサービスのminReplicaCountを維持、AuroraClusterの停止スケジュールを変更
停止処理 指定した停止時間 マイクロサービスとAuroraClusterを停止
リセット処理 翌営業日のAM10:00 設定を元の状態に戻す

停止延期処理を本来の停止時間の30分前に実行するのは、KEDAのcron triggerが0:00にminReplicaCountを0にしてPodを削除するためです。その前にminReplicaCountを1以上に変更し、Podの削除を防ぎます。また、30分という時間は、PR作成・マージ・Fluxによるクラスタへの同期が完了するのに十分な余裕を持たせています。

func scheduleStop(env Env, stopTime *time.Time) (ScheduleStopResult, error) {
    // ...省略...
    // 停止延期処理: 本来の停止時間の30分前に実行
    expandScheduleName := "sc8leon-expand-stop-time"
    expandScheduleExpression := fmt.Sprintf("at(%s)",
        time.Date(stopTime.Year(), stopTime.Month(), stopTime.Day(), 0, 0, 0, 0, stopTime.Location()).
            Add(-30*time.Minute).Format("2006-01-02T15:04:05"))
    expandSchedulePayload := fmt.Sprintf(`{"env": "%s", "command": "expand-stop-time", "schedule_time": "%s"}`,
        env, stopTime.Format("2006/01/02 15:04:05"))

    if err = awscli.CreateScheduleForLambda(expandScheduleName, expandScheduleExpression, "sc8leon", expandSchedulePayload); err != nil {
        return ScheduleStopResult{}, errors.Join(err, errors.New("failed to create eventbridge schedule"))
    }

    // 停止処理: 指定された時間に実行
    stopScheduleName := "sc8leon-stop"
    stopScheduleExpression := fmt.Sprintf("at(%s)", stopTime.Format("2006-01-02T15:04:05"))
    stopSchedulePayload := fmt.Sprintf(`{"env": "%s", "command": "stop"}`, env)

    if err = awscli.CreateScheduleForLambda(stopScheduleName, stopScheduleExpression, "sc8leon", stopSchedulePayload); err != nil {
        return ScheduleStopResult{}, errors.Join(err, errors.New("failed to create eventbridge schedule"))
    }

    // リセット処理のスケジュールも登録
    registerResult, err := RegisterResetFunc(env, awscli, *stopTime)
    // ...省略...
}

停止延期処理では、マイクロサービスのminReplicaCountを維持するだけでなく、AuroraClusterのEventBridge Schedulerの停止時間も変更します。

func changeScheduleRDSCluster(clusterSetting *RDSClusterSetting, awscli *AWSClient, stopTime time.Time) (ChangeScheduleRDSClusterResult, error) {
    scheduleExpression := fmt.Sprintf("cron(%d %d ? * MON-FRI *)", stopTime.Minute(), stopTime.Hour())
    if err := awscli.UpdateEventBridgeSchedule(clusterSetting.AWSAccountID, clusterSetting.StopScheduleName, scheduleExpression); err != nil {
        return ChangeScheduleRDSClusterResult{}, errors.Join(err, errors.New("failed to update eventbridge schedule"))
    }
    return ChangeScheduleRDSClusterResult{
        ScheduleName:       clusterSetting.StopScheduleName,
        ScheduleExpression: scheduleExpression,
    }, nil
}

自動リセット

sc8leonで変更した設定は、翌営業日に自動でリセットされます。これにより、戻し忘れによるゼロスケール解除状態の継続を防いでいます。

func RegisterResetFunc(env Env, awscli *AWSClient, resetTime time.Time) (RegisterResetFuncResult, error) {
    scheduleName := fmt.Sprintf("sc8leon-reset-%s", resetTime.Format("20060102"))

    // 既に同日のリセットスケジュールが登録されている場合はスキップ
    sc, err := awscli.GetSchedule("default", scheduleName)
    if err == nil && sc != nil {
        return RegisterResetFuncResult{AlreadyRegisterd: true, ...}, nil
    }

    // 平日はAM10:00、土日はPM10:00にリセット
    resetHour := 10
    if resetTime.Weekday() == time.Saturday || resetTime.Weekday() == time.Sunday {
        resetHour = 22
    }

    scheduleExpression := fmt.Sprintf("at(%s)", time.Date(..., resetHour, 0, 0, 0, ...).Format("2006-01-02T15:04:05"))
    payload := fmt.Sprintf(`{"env": "%s", "command": "reset"}`, env)

    awscli.CreateScheduleForLambda(scheduleName, scheduleExpression, "sc8leon", payload)
    return RegisterResetFuncResult{...}, nil
}

リセット処理では以下を実行します。

  • マイクロサービス: minReplicaCountを0に戻すPRを作成・マージ
  • AuroraCluster: EventBridge Schedulerの起動/停止時間を設定ファイルに記載された元の値に戻す

使い方

sc8leonはSlack Workflowから実行できます。環境ごとに専用のSlackチャンネルが用意されており、ワークフローを選択して実行するだけで環境の起動・停止が可能です。

Slack WorkflowとLambdaを組み合わせたChatOpsの仕組みについては、以下の記事も参照してください。

techblog.zozo.com

操作手順

  1. 該当環境のSlackチャンネルでワークフローを選択(例:sc8leon-start-dev
  2. ポップアップで対象環境を選択し、送信ボタンを押下
  3. ワークフローが投稿したメッセージのスレッドで「Run command」を選択
  4. 実行完了後、Slackに結果が通知される

各機能(Start / Stop / Schedule Start / Schedule Stop / Status)で同様の手順で操作できます。Schedule Start / Schedule Stopでは、起動・停止したい日時も選択します。

導入効果

sc8leonの導入により、以下の効果が得られました。

課題 導入前 導入後
運用負荷 マニフェスト修正とPR作成・レビューで最低2名の対応が必要 Slackから1人で即時実行可能
対象の多さ 多数のマイクロサービスを手動で起動、対応漏れのリスク 設定ファイルで一括管理、自動で全サービスを起動
属人化 マニフェスト修正方法を知っている人しか対応できない 誰でもSlackから操作可能
戻し忘れ MinReplicaを元に戻すPRを忘れるリスク 翌営業日に自動リセット

特に、深夜・休日の緊急対応時において開発者が自身で環境を起動できるようになったことで、SREへの依頼待ち時間がなくなり、迅速な対応が可能になりました。

まとめ

本記事では、ゼロスケールされた開発環境をSlackから起動できるツール「sc8leon」を紹介しました。

GitOpsを導入した環境では、kubectlによる直接変更ができないため、マニフェスト修正→PR作成→マージという手順が必要でした。sc8leonはこの手順を自動化し、GitHub Appsによる認証とLambda上でのインメモリGit操作を組み合わせることで、Slackからの簡単な操作で環境を起動できるようにしました。

また、EventBridge Schedulerのone-time schedulesを活用し、起動時間の前倒しや停止時間の延期といったスケジュール機能を実現しています。さらに、翌営業日に自動でリセットされるため、戻し忘れを防止できます。

GitOps環境でマニフェスト変更を自動化する際のポイントは、GitHub Appsによる認証とブランチ保護ルールのバイパス設定、そしてLambda環境でのGit操作です。これらを組み合わせることで、人手を介さずにPRの作成からマージまでを完結させることができます。

sc8leonの導入により、深夜・休日でも開発者自身が環境を起動できるようになり、SREへの依頼待ち時間がなくなりました。開発環境のゼロスケール運用において、運用負荷と利便性のバランスを取る1つの解決策として参考になれば幸いです。

ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。

corp.zozo.com

カテゴリー