GithubActionsを活用して社内JenkinsとのChatOpsを実現する

AvatarPosted by

こんにちは、多分クライアントエンジニアの田中です。今回はGithubActionsを利用して社内JenkinsとのChatOpsを早期実現した事例をご紹介します。

GithubActionsとJenkins

GithubActionsはGithubのリポジトリを利用したCIを行う事が出来るサービスです。リポジトリからプッシュやプルリクエストをGithub側でフックして様々なCIを行う事ができ、処理にはDockerを利用出来るのでわりと汎用性もある為、これからが期待です。

それに対してJenkinsはCIの老舗とも取れる昔ながらの少し温かみがあるGUIでの設定を行えるCIツールになります。オンプレミスな環境での独立的な稼働が出来るのでどこかのサービスに縛られる事もありません、拡張性はJenkinsをよく知る者であればあるほど柔軟性の高い構築を可能とします。

ゲーム開発のビルド環境

現在のゲーム開発のクライアントのビルド環境においてはコンテナ等を利用したビルド環境を用意しづらく(MacOSが必須な場面もある)現在もJenkinsを利用したビルド環境を構築している所がまだまだ多い印象です。

ビルドの比重も重めであり、時間課金になるCIは使いづらい場面も多いかと思います。

社内JenkinsをChatOpsに対応させる

実は今回オンプレミス環境のJenkinsを外部から実行したいという要望の為、実現しました。この場合はVPN接続がわりと簡単な解決策ではありますが、様々な理由でVPNが利用できない開発者が発生した為です。

ChatツールであるSlackからJenkinsへのアクセスを行えるとビルドのオペレーションが可視化されて何かしらのアクションへも繋がりやすく業務改善としても良いかと思います。

Jenkinsの問題点

ChatOps対応していきましょうとなるところでJenkinsを利用していると問題点がある事に気づくかと思います。

  • 外部公開アドレス問題(会社のIPほぼ公開になるからDMZ用に回線契約する必要もある?)
  • 移設コストがかかる(必要なら)
  • セキュリティ問題に常に晒される

Jenkinsへのアクセスには外部からの公開アドレスやDMZ等のセキュリティにも気を使う必要があるとても大変な作業になります。正直、社内だけの無菌室の様な環境からインターネットの大海原へ唐突に露出させるのはとてもリスキーな実現方法です。

そしてJenkinsはGUI設定項目が多いのと汎用性が無駄に高いのが仇となり、なかなか別のCIツールに移行するのが難しいのもあります。

(蹂躙されるJenkinsおじさんの住処の図)

解決策

そこで考えた解決策がこちらになります。

実はGithubActionsにはAPI経由でのビルド開始機能とベアメタル環境で稼働可能なSelf-hosted runnerなる物が存在します。その為、この2つの機能を組み合わせる事により、簡易的なAPIのプロキシとして機能させる事が可能です。

Self-hosted runnerには社内と社外を繋ぐ役割と同時にパラメータの受け渡しをビルドの実行時にして貰います。これによりJenkins側は内部のAPIリクエストを処理するだけで、GithubActions側はGithubに保守された公開APIが使えます。認証もついてくる!やったー!

Self-hosted runnerをリポジトリへ追加

GithubのリポジトリのタブからSettings=>Actionと開いて、一番下のAddRunnerから追加できます。AddRunnerを押すとselfRunnerの展開方法が出てくるので、Jenkinsにアクセス出来るPCへインストールしましょう。初回起動時はウィザードでインストールしたPCの情報を求められますが、複数のRunnerを利用しない限りは特に詳しく設定しなくても問題ありません。

GithubActionsのYAML設定

name: jenkins

on:
  repository_dispatch:
    types: [jenkins_api]

jobs:
  jenkins-api:
    if: github.event.action == 'jenkins_api' # 他のjob実行で動かない様に
    runs-on: self-hosted
    steps:
    - name: JobAPI
      run: |
        curl http://hoge/jenkins/job/dev/job/build-test-job/buildWithParameters \
          --user ${{ secrets.JENKINS_USER_SECRET }} \
          --data deploy_target=${{ github.event.client_payload.deploy_target }} \
          --data branch=${{ github.event.client_payload.branch }} \
          --data server_branch=${{ github.event.client_payload.server_branch }}
      shell: bash

repository_dispatchruns-on: self-hostedが今回の肝です。

repository_dispatchにより、API経由でのジョブの始動を有効にしています。そして、このrepository_dispatchgithub.event.client_payload.hogeの様に記述する事により、APIを叩いた時にURLに流し込んだデータを受け取れます。その為、パラメータ的なビルドにも対応が可能です。

runs-on: self-hostedに関してはジョブが動く時のRunnerタイプを指定しており、このジョブはSelf-hosted Runnerだけで行う様に設定しています。

GithubAPIから叩く(TypeScript)

import * as functions from "firebase-functions";

const config = functions.config();
const GITHUB_API_URL = 'https://api.github.com';

// Prepare axios for GitHub API
const axiosBase = require('axios');
const apibase = axiosBase.create({
    baseURL: GITHUB_API_URL,
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        accept: 'application/vnd.github.v3+json',
        Authorization: 'token ' + config.github.token,
    },
    responseType: 'json'
})

export type Payload = {
    deploy_target: string;
    branch: string;
    server_branch: string;
};


export async function PostDispatches(payload: Payload) {
    const result = await apibase.post("/repos/hogehoge/hugehuge/dispatches", {
        "event_type": "jenkins_api",
        "client_payload": {
            "deploy_target": `${payload.deploy_target}`,
            "branch": `${payload.branch}`,
            "server_branch": `${payload.server_branch}`,
        }
    }).then(function(response: { data: any; status: any; statusText: any; headers: any; config: any; }) {
        console.log(response.data);
        console.log(response.status);
        console.log(response.statusText);
        console.log(response.headers);
        console.log(response.config);
    }).catch(function (error: { response: { data: any; status: any; statusText: any; headers: any; }; request: any; message: any; config: any; }) {
        if (error.response) {
            console.log(error.response.data);
            console.log(error.response.status);
            console.log(error.response.statusText);
            console.log(error.response.headers);
        } else if (error.request) {
            console.log(error.request);
        } else {
            console.log('Error', error.message);
        }
        console.log(error.config);
    });
    return result;
}

今回SlackのBoltライブラリを利用した関係で、TypeScriptで書きましたがほぼdispatchesのAPI(create-a-repository-dispatch-event)を叩けば問題ないと考えて下さい。

このdispatchesを公開APIとして利用する事で、後は使用するChatBot側の実装に注力する事が出来ます。コマンドだけではなく、モーダルダイアログでJenkinsに近いGUIを用意してあげると良いでしょう。

ChatOps対応の先に

今回の実装は簡単なModel(Jenkins) View(Slack) Controller(GithubActions) に近い構築がされています。

その為、実装の差し替えがJenkinsだけの時と違い変えやすくなります。これを利用してChatOpsを先に推進する事で社内でのJenkinsの表面的な利用率を下げて脱Jenkinsも展開しやすいのと、ただ普通にGithubActionsへのCIツール移行の先駆けとしても走り出しやすいと考えられます。

そしてSelf-Runnerを利用する場合、時間利用料金は無料です。いい塩梅でランナーを使い分ければ料金も安く上がり、部分的にホストランナー(githubがホストしてくれてるやつ)へ振る事も可能な為、ゲーム開発におけるCI周りのネックを解決出来る可能性を秘めていると考えています。

今回はGithubActionsの本来の利用方法としてデファクトからは外れていると考えられる利用法を紹介してきましたが、この様な活用も出来ますのでこの機に活用してみては如何でしょうか。