🐧

GitBucket の CI 用プラグインで CI/CD:Linux 使い(略)Advent Calendar 2024

2024/12/15に公開

はじめに

これは「Linux 使いになりたい人のための Advent Calendar 2024」の記事です。

筆者は、Web エンジニアを志望する人には、セルフホスト Git サービスを稼働させて利用することをオススメしています。もし Git を使ったことがないなら、Git を学ぶときに、セルフホスト Git サービスを稼働させることも視野に含めながら学習するのが効率的だと考えています。

GitBucket の CI 用プラグインで CI/CD

前回はセルフホスト Git サービスを稼働させるにあたり、GitBucket について説明しました。GitBucket については、簡易な CI/CD 環境の構築で良いなら、プラグインを追加するだけですぐに実現できる点を筆者は高く評価しています。

今回は、GitBucket の CI 用プラグインで CI/CD を実現する方法について説明します。

GitBucket の CI 用プラグイン

GitBucket には CI/CD に利用できるプラグイン gitbucket-ci-plugin というものがあります。これをインストールすると、GitBucket にビルド処理を実行させるためのスクリプトを登録できるようになります。

ここでは、次のディレクトリー構成でファイルを用意して、gitbucket-ci コンテナーを使って動作確認をする方法について説明します。また、gitbucket-ci ディレクトリーを ${GITBUCKET_CI_DIR} と表記します。

gitbucket-ci/
├── build-image/
│   ├── Dockerfile
│   ├── compose.yaml
│   └── script/
│       └── install-gitbucket-ci-plugin.sh
├── compose.yaml
└── script/
    └── build.sh

gitbucket-ci-plugin のインストール方法

gitbucket-ci-plugin は、GitBucket のインストールディレクトリーにある plugins ディレクトリーへ gitbucket-ci-plugin の開発サイトである https://github.com/takezoe/gitbucket-ci-plugin で配布されている gitbucket-ci-plugin.jar ファイルを置くだけで使えるようになります。

これをインストールするスクリプトは次のようになります。GitBucket のインストールディレクトリーは ghcr.io/gitbucket/gitbucket:4.41.0 のイメージに合わせてあります。

#!/bin/sh

if [ "${VERSION}" = "" ]; then
    VERSION=1.11.0
fi
if [ "${PLUGIN_DIR}" = "" ]; then
    PLUGIN_DIR=/gitbucket/plugins
fi
BASE_URL=https://github.com/takezoe/gitbucket-ci-plugin
FILE_NAME="gitbucket-ci-plugin-${VERSION}.jar"
URL="${BASE_URL}/releases/download/${VERSION}/${FILE_NAME}"

if [ ! -e "${PLUGIN_DIR}" ]; then
    mkdir -p "${PLUGIN_DIR}"
fi

curl -s --output "${PLUGIN_DIR}/${FILE_NAME}" -L "${URL}"

このスクリプトでは curl コマンドを使って、gitbucket-ci-plugin の公式のダウンロードページからファイルをダウンロードしています。バージョンは 1.11.0 のものを使っています。これで、gitbucket-ci-data ボリューム内に gitbucket-ci-plugin-1.11.0.jar が保存されます。

実行するには GitBucket のインストールディレクトリーへ書き込み可能なアカウントで次のように実行します。

cd `${GITBUCKET_CI_DIR}`
sh build-image/script/install-gitbucket-ci-plugin.sh

gitbucket-ci 用イメージ用ファイル

次に、gitbucket-ci 用イメージ用ファイルを用意します。

ベースとなるイメージに ghcr.io/gitbucket/gitbucket:4.41.0 を指定し、プラグインの gitbucket-ci-plugin をインストールするために install-gitbucket-ci-plugin.sh を実行します。

運用レベルで利用する場合は、一般ユーザーでの実行としたいところですが、そういった処理も含めると少し大変なので簡潔にするために、ここでは最低限の処理にとどめます。

# プラグイン入手処理用
FROM ghcr.io/gitbucket/gitbucket:4.41.0 AS build-env

COPY ./script /script
RUN sh /script/install-gitbucket-ci-plugin.sh

# 実際のイメージビルド用
FROM ghcr.io/gitbucket/gitbucket:4.41.0 AS prod-env

COPY --from=build-env /gitbucket/plugins /gitbucket/plugins

イメージをビルドするにあたっては、次のようにコマンドで実行可能です。

cd ${GITBUCKET_CI_DIR}
cd build-image
docker image build . --tag gitbucket-ci:4.41.0

ただし、この場合は --tag gitbucket-ci:4.41.0 のようにして --tag オプションでイメージ名を指定する必要があります。

これについて、スクリプトで対応しても良いのですが、Docker 環境での表現としては compose.yaml ファイルに記述しておくのが良いでしょう。シェルスクリプトは最終的なイメージ名を判定するには処理の流れを追う必要がありますが、compose.yaml は宣言的なので、image: を見れば最終的なイメージ名がすぐにわかります。

ここでは ${GITBUCKET_CI_DIR}/build-image/compose.yaml に次のファイルを用意して対応します。

name: gitbucket-ci-build
services:
  gitbucket-ci-build:
    build:
      context: .
      dockerfile: ./Dockerfile
    image: gitbucket-ci:4.41.0
    container_name: gitbucket-ci-build
    hostname: gitbucket-ci-build
    tty: true

これをビルドするコマンドについては、スクリプト ${GITBUCKET_CI_DIR}/build-image/build.sh で用意しておきます。

#!/bin/sh

SCRIPT_DIR=$(dirname "$0");
DC_DIR=$(cd "${SCRIPT_DIR}" || exit 0; pwd)
DC_FILE="${DC_DIR}/compose.yaml"

# shellcheck disable=SC2068
docker compose -f "${DC_FILE}" build $@

以上で準備はおしまいです。

gitbucket-ci 用イメージのビルド

gitbucket-ci 用イメージのビルドをするには ${GITBUCKET_CI_DIR}/build-image/build.sh を実行します。

cd ${GITBUCKET_CI_DIR}
sh build-image/build.sh

実行例は次のようになります。

$ build-image/build.sh
 => [gitbucket-ci-build] exporting to image                     0.0s
 => => exporting layers                                         0.0s
 => => writing image sha256:631cecd1442(略)                   0.0s
 => => naming to docker.io/library/gitbucket-ci:4.41.0          0.0s
 => [gitbucket-ci-build] resolving provenance for metadata file 0.0s

これで、ローカルマシンに gitbucket-ci:4.41.0 の Docker イメージが用意できます。

gitbucket-ci 用イメージの利用

次に、gitbucket-ci 用イメージを利用するために ${GITBUCKET_CI_DIR}/compose.yaml を用意します。

name: gitbucket-ci
services:
  gitbucket-ci:
    image: gitbucket-ci:4.41.0
    container_name: gitbucket-ci
    hostname: gitbucket-ci
    tty: true
    ports:
        - 127.0.0.1:8080:8080
        - 127.0.0.1:29418:29418
    volumes:
      - type: volume
        source: gitbucket-ci-data
        target: /gitbucket

volumes:
  gitbucket-ci-data:
    name: gitbucket-ci-data

これを使って、gitbucket-ci コンテナーを起動します。起動スクリプト、停止スクリプトを用意しておきます。

起動スクリプト ${GITBUCKET_CI_DIR}/script/run.sh は次のとおり。

#!/bin/sh

SCRIPT_DIR=$(dirname "$0");
DC_DIR=$(cd "${SCRIPT_DIR}/.." || exit 0; pwd)
DC_FILE="${DC_DIR}/compose.yaml"

docker compose -f "${DC_FILE}" up -d

停止スクリプト ${GITBUCKET_CI_DIR}/script/run.sh は次のとおり。

#!/bin/sh
SCRIPT_DIR=$(dirname "$0");
DC_DIR=$(cd "${SCRIPT_DIR}/.." || exit 0; pwd)
DC_PROJ_NAME=$(basename "${DC_DIR}")

docker compose -p "${DC_PROJ_NAME}" down

スクリプトの用意ができたら起動します。

cd ${GITBUCKET_CI_DIR}
sh script/run.sh

起動ができたら、gitbucket-ci-plugin の JAR ファイルがあることを確認しておきます。

docker compose -p gitbucket-ci exec gitbucket-ci \
    ls /gitbucket/plugins/gitbucket-ci-plugin-1.11.0.jar

実行例は次のようになります。

$ docker compose -p gitbucket-ci exec gitbucket-ci \
    ls /gitbucket/plugins/gitbucket-ci-plugin-1.11.0.jar
/gitbucket/plugins/gitbucket-ci-plugin-1.11.0.jar

次のようになる場合は、Docker イメージ作成か compose.yaml の作成において、何か問題が起きています。正しいコードとなっているか確認しましょう。

$ docker compose -p gitbucket-ci exec gitbucket-ci \
    ls /gitbucket/plugins/gitbucket-ci-plugin-1.11.0.jar
ls: cannot access '/gitbucket/plugins/gitbucket-ci-1.11.0.jar': No such file or directory

GitBucket の初期設定

それでは、GitBucket の初期設定をして使えるように環境を用意してみましょう。user001 ユーザの登録と、user001/sample-ci リポジトリの作成をします。

Docker 版の GitBucket では初期の管理者アカウントが用意されていて、次の情報でサインインできます。

  • ID: root
  • Pass: root

管理者アカウントでサインインしたら、次の URL を開いてパスワードを変更しておきます。

次に、ユーザーの新規作成のため、次の URL を開きます。

この画面で新規に user001 ユーザの登録をします。試用する場合のメールアドレスは user001@localhost などとしておけば良いです。

ユーザーの新規作成ができたら、次の URL を開いて root ユーザーのサインアウトをします。

それから、次の URL を開いて user001 ユーザーで GitBucket へログインし直します。

それから、次の URL を開いて sample-ci リポジトリーを作成します。

「Repository name」に sample-ci を指定します。「Description」は空でかまいません。

次にリポジトリー作成時には公開するかしないか、README を最初から作成しておくか、などの選択肢があります。ここでは、次の選択肢を指定します。

  • Private
  • Private、Initialize this repository with a README

それから「Create repository」をクリックすると sample-ci リポジトリが作成されます。

以上で GitBucket の初期設定はおしまいです。

CI/CD 実行用スクリプトの用意

ここで、CI/CD 実行用スクリプト ${GITBUCKET_CI_DIR}/sample/ci-build-01.sh を次の内容で用意します。

#!/bin/sh

LOG_FILE=/gitbucket/repositories/user001/sample-ci-build.log
ARTIFACT_DIR=/gitbucket/repositories/user001
ARTIFACT_FILE_NAME=sample-ci-release.tgz

# ブランチ情報は環境変数 CI_BUILD_BRANCH に設定されている前提
BRANCH_NAME=${CI_BUILD_BRANCH}

# 処理の開始
echo "updated begin" | tee ${LOG_FILE}

# 時刻の記録
date | tee -a ${LOG_FILE}

# ブランチの情報で処理を分岐
if [ "${BRANCH_NAME}" = "main" ]; then
    echo "${BRANCH_NAME}" | tee -a ${LOG_FILE}
    cat README.md
elif [ "${BRANCH_NAME}" = "prod" ]; then
    echo "prod" | tee -a ${LOG_FILE}
    # ここではコメントにしてますが、例えば ssh コマンドを使ってデプロイ処理を実行
    # ssh user001@targethost.example.jp "cd /var/www/html && git --git-dir=.git pull origin main"
    tar czf "${ARTIFACT_DIR}/${ARTIFACT_FILE_NAME}" README.md
fi

# 処理の終了
echo "updated end" | tee -a ${LOG_FILE}

内容については簡単なスクリプトなので説明を省略します。実行後、gitbucket-ci:/gitbucket/repositories/user001/sample-ci-build.log にログが、gitbucket-ci:/gitbucket/repositories/user001/sample-ci-release.tgz にビルド結果のファイルが残るようにしてあります。

なお、ビルドスクリプトが実行されるときに、現在のターゲットとなるブランチ情報は、環境変数 CI_BUILD_BRANCH へ GitBucket により自動で設定されます。これを使ってブランチによって処理を切り替えることができるようになっています。

GitBucket で CI/CD 実行

作成された user001/sample-ci リポジトリ用の URL http://localhost:8080/user001/sample-ci を開くと、サイドメニューに「Build」があります。

メニューの「Build」をクリックすると http://localhost:8080/user001/sample-ci/build の画面になり、このページにはビルドの履歴が表示されます。

最初はビルドの設定がないので、開いてもビルドの履歴はありません。

それから、ビルドの設定のために、次の URL を開きます。メニューの「Settings」をクリックして表示される画面で「Build」タブをクリックしても良いです。

「Build」の「Enable build」をチェックして、「Build script」を選択します。ここに ${GITBUCKET_CI_DIR}/sample/ci-build-01.sh のスクリプトを入力します。

それから、「Apply changes」をクリックして反映します。

次に左のメニューにある「Build」をクリックして http://localhost:8080/user001/sample-ci/build の画面を表示します。このページに「Run build」のボタンが増えているので、これをクリックすると、先程「Build script」に登録したスクリプトを実行できます。

実行が終了すると結果が表示されます。

成功すると「Success」、失敗すると「Failure」が先頭に表示されます。対象となったブランチ、実行したユーザーの情報が表示されます。#1 の部分はリンクとなっていて、ビルド処理時のコンソール出力の結果を見ることができます。

コンソール出力の結果については、次のようになります。

git clone user001/sample-ci
git checkout 715691af54d4b722905b845ba4bac3e6050df71e
updated begin
Sun Dec 15 02:11:15 AM UTC 2024
main
sample-ci
===============
updated end

以上で、GitBucket を使って CI/CD 用のスクリプトによるビルドができることがわかったはずです。

git クライアントからの利用

用意した GitBucket の Git リポジトリーを git クライアントから利用してみましょう。

ここでは、gitbucket-ci コンテナーを利用して動作確認します。ユーザーが root となってしまいますが、試用ということで割り切ります。

最初に git パッケージを apt コマンドでインストールして git コマンドを使えるようにします。

docker compose -p gitbucket-ci exec gitbucket-ci apt update
docker compose -p gitbucket-ci exec gitbucket-ci apt -y install git

次に git config でユーザー情報を設定します。

docker compose -p gitbucket-ci exec gitbucket-ci \
    git config --global user.email "user001@localhost"
docker compose -p gitbucket-ci exec gitbucket-ci \
    git config --global user.name "user001"

次のように gitbucket-ci:/root/.gitconfig が作成されていれば大丈夫です。

$ docker compose -p gitbucket-ci exec gitbucket-ci cat /root/.gitconfig
[user]
        email = user001@localhost
        name = user001

それから、作業用の /workspace ディレクトリーを作成します。

docker compose -p gitbucket-ci exec gitbucket-ci \
    mkdir /workspace

準備ができたら、gitbucket-ci コンテナーの bash へ docker compose exec コマンドでアタッチします。

docker compose -p gitbucket-ci exec \
    --workdir=/workspace gitbucket-ci \
    bash

GitBucket の sample-ci のリポジトリーについて、URL は http://localhost:8080/user001/sample-ci を開いた画面で確認できて、次のものになります。

HTTP アクセス時の認証情報は、user001 の GitBucket へのログイン時に使うものを BASIC 認証の形で指定できます。ここでも試用という前提で、コマンドラインで指定します。

次の例は、リポジトリをクローンしたときのものです。プロンプトの root@gitbucket-ci:/workspace# はすべて # の形で省略してあります。また、usr001 ユーザーのパスワードは pass001 だとしてあります。

# GIT_USER_NAME=user001
# GIT_USER_PASSWORD=pass001
# git clone http://${GIT_USER_NAME}:${GIT_USER_PASSWORD}@localhost:8080/git/user001/sample-ci.git
Cloning into 'sample-ci'...
remote: Counting objects: 3, done
remote: Finding sources: 100% (3/3)
remote: Getting sizes: 100% (2/2)
remote: Compressing objects: 100% (63/63)
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
# cat sample-ci/.git/config 
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = http://user001:pass001@localhost:8080/git/user001/sample-ci.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
        remote = origin
        merge = refs/heads/main

sample-ci リポジトリをクローンしたら、README.md の内容の確認と、更新をしてみましょう。ここでは次のように cat コマンドで確認、echo コマンドで update 1 の文字列追加をしてみます。

# cat sample-ci/README.md 
sample-ci
===============
# echo "update 1" >> sample-ci/README.md
# cat sample-ci/README.md 
sample-ci
===============
update 1

現在のブランチについても確認しておきましょう。git branch コマンドを実行すると main ブランチとなっていることがわかります。ちなみに -C オプションに Git リポジトリのパスを指定できます。こうすると、cd コマンドを使ったカレントディレクトリーの変更をする必要がないので便利です。

# git -C /workspace/sample-ci branch
* main

それから、git add コマンドで README.md ファイルをステージングします。

git -C /workspace/sample-ci add README.md

次に、git commit コマンドでリポジトリへ README.md の変更を反映します。

git -C /workspace/sample-ci commit -m "update 1"

最後に、git push でリモートリポジトリへ README.md を変更したコミットを反映します。

git -C /workspace/sample-ci push

参考までに git push No実行例は次のようになります。

# git -C /workspace/sample-ci push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 254 bytes | 254.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: Updating references: 100% (1/1)
To http://localhost:8080/git/user001/sample-ci.git
   715691a..ecbdcc0  main -> main

それから、GitBucket の user001/sample-ci リポジトリーのビルド画面を確認するため、次の URL を開きます。

すると、ビルド処理が自動で実行されていることが確認できます。

ここまで説明した順番に従って実行している場合は #2 のビルドが増えているはずです。実行結果は次の URL で確認できます。

また、これを開いた時、実行結果の内容は、下記のようになっていて README.md の内容が更新されていることがわかるはずです。

git clone user001/sample-ci
git checkout ecbdcc0c9dea50d278c0f4beda6a116e6ff12693
updated begin
Sun Dec 15 02:46:37 AM UTC 2024
main
sample-ci
===============
update 1
updated end

動作確認ができたら gitbucket-ci コンテナーは削除しておきます。これで、gitbucket-ci コンテナー内で試しに用意した git コマンドや gitbucket-ci:/workspace も削除されます。

cd ${GITBUCKET_CI_DIR}
sh script/down.sh

なお、GitBucket が提供するリモートリポジトリは gitbucket-ci-data ボリュームに残っているので、 gitbucket-ci コンテナーを起動すると使えます。

おわりに

ここで紹介したように gitbucket-ci-plugin を使うと、簡単な CI/CD 環境であれば、すぐに用意できます。個人開発用途であれば、ローカルマシン上の Docker で日常的に使うことも十分できます。

注意点としては、手軽に CI/CD 環境を用意するなら、Java ランタイム環境で GitBucket を動かした方が良いといった点でしょうか。ホスト OS が Linux なら、Java ランタイム環境の GitBucket から OS で使えるスクリプトがそのまま実行できます。ホスト OS で docker コマンドが使えるにようになっていたら、ビルド用のスクリプトから docker コマンドの実行ができるように調整すれば、それで Docker 環境による CI/CD もできるようになります。

ここで問題になるのは Windows 環境の Java ランタイム環境で GitBucket を動かす場合は、ビルド用のスクリプトから docker コマンドの実行ができるように調整するのが少し大変そうだという点があります。その調整をするなら、GitBucket を Docker 環境で動作させた方が楽だろうという印象を持っています。ただし、この場合は GitBucket のコンテナーに開発で必要なものを全部インストールして動くようにしておく必要がでてきます。

このあたりは、開発で使う Docker イメージをどのように用意していくのかというポリシーにも関係してくるので、どうするのが良いか判断するのは難しいところです。

ここで、本格的に CI/CD 環境を用意しようと思うと、CI/CD から Docker 環境を使ってテストコードの実行をしたいと思うようになります。このあたりがセルフホスト Git サービスの構築について難しいところです。開発者のレベルによって、必要と感じる CI/CD 環境のハードルが変わるのです。

ですから、筆者は、このあたりは割り切って自分がわかる範囲で動く環境を用意していけば良いと考えています。自分のスキルがアップしたら、そのときにより良い環境に移行すれば良いのです。どうせ自分がわかる範囲でしか運用できません。そういった意味では、セルフホスト Git サービスは自分向けのものなので、文句を言うのは自分ですし、改善できるのも自分だけで、気軽なものです。

筆者も、クラウドサービス系の充実した CI/CD 環境を、コストをかけて個人開発で使うのは厳しいので、セルフホスト Git サービスで対応しようと考えたところから、色々と調べたのですが、結論としてはセルフホスト Git サービス専用のマシンを用意するのと、CI/CD 実行用の専用マシンを用意するのがベストとなっています。ただ、これは趣味レベルの個人開発だと設備としては過剰な気がしています。フリーランスレベルだと用意しても良いのだろうという印象です。

そんなことを感じながら、CI/CD 環境の構築方法については結構詳しくなっていると自負しています。それで、仕事では CI/CD 環境を用意してどんどん活用しているので良いのですが、個人開発のアプリケーション開発では CI/CD の実践がまだ充実しているというところまでいっていないという、そんな状況だったりします。

いずれにせよ、セルフホスト Git サービスの運用をしてみると、スキルアップにつながる点については間違いないと考えています。

Discussion