追記
実例サンプルつくりました
GiteaとJenkinsによるCI/CDの実例サンプル(Python / PHP / Perl でテストとデプロイを自動化する)
はじめに
「いまあるリポジトリからブランチきって進めてくれればいいよ」
よくある会話なんですよね?これって。
やりかたが分からずコマンドレベルで教えてもらいました。ずびばぜん。。。
ナウでヤングな開発をしたかったのでGitのべんきょうをしようとおもいました。
ついでにテストとかデプロイとかの自動化をしたかったので、環境を構築してみました。
手段の目的化ばんざい!
べろべろーとコピペすると、VPSやらオンプレ環境でGitサーバ+CIツールが使えるようになります。
このエントリを読むとできること
- orginizationという組織のrepo-Aリポジトリにpushすると、、、
- GiteaからSlackおよびJenkinsのwebhookをたたく
- Jenkinsではrepo-Aにpushがあったという通知をGiteaプラグインが受ける
- pushされたrepo-AにJenkinsfileがあるかチェックする
- Jenkinsfileがあればそれをもとにpipeline処理を行う
- pipeline処理の結果を判別し
- JenkinsからSlackのwebhookをたたく
ということができる。
「CI/CDする」までは解説できていませんので、「CI/CDができる環境を作る」です(よね?)
この環境を構築したうえで、ちゃんとテスト自動化できるようなコードを書いたりpipeline処理を書いたり、デプロイを自動化するための処理を書いたりすることで、「CI/CDする」んだと思います。
本エントリの解説では、組織単位で発生する全イベントでwebhookをたたくようになっているので、
- リポジトリごとに設定したり
- ブランチ毎に設定したり
- マージされた場合だけという設定をしたり
とやっていくと、必要なことができるようになるんではないかとおもいます。
あと、pipeline処理の結果はマークダウン形式で常時確認できるので、リポジトリのREADME.mdに書いておけば、そのリポジトリ(のブランチ)の状態がわかるようになる。
対象機器および環境
検証環境
- CentOS8 (8.2.2004)
- Gitea (1.13.0+dev-389-ge17e3f71f)←記事作成時点のMasterブランチものです
- Jenkins (2.251)
構築パラメータ
私用環境で検証しています。
FQDNやらポート番号やらは、必要に応じて修正してください。
(あとで紹介するコピペコマンドを修正する必要があります)
項目 | Gitリポジトリマネージャ | CI/CDツール |
---|---|---|
Software | Gitea | Jenkins |
Path | /opt/gitea | /opt/jenkins |
Port | 3000 | 8080 |
User | gitea | gitea |
FQDN(A) | vmnetserv03.prosper2.net | vmnetserv03.prosper2.net |
FQDN(CNAME) | gitea.prosper2.net | jenkins.prosper2.net |
また、GiteaとJenkinsを動作させるために、以下のユーザを新規作成します。
適当なユーザがいれば、それを利用してもOKですが、シェルが必要なので、nobodyなどの /sbin/nologin
や /bin/false
ではダメです。
項目 | 値 |
---|---|
ユーザ名 | gitea |
グループ名 | gitea |
Shell | /bin/bash |
Home | /opt/gitea |
注意事項
すべてHTTPでつくっています。(GitはSSHですけど)
HTTPSでやるなら証明書発行したり、Nginxでsslをかぶせたりしてください。
プライベートPKIを利用するなら、JenkinsがJAVAなのでkeytoolでルートとか中間のCAをトラストストアに追加するか、チェーン済み証明書をNginxに配置するなど工夫してください。
作業内容
CentOS8の準備
CentOS8をクリーンインストールしておく。(最小構成でOK)
ESXi6.7にCentOS8を最小構成で構築
サーバ構築
さぁ、コピペの時間だ。
あ、コピペコマンドが長くなるのがイヤだったので、 sudo
してません。
すべて root
で作業してください。
事前準備
- タイムゾーンの修正
- SELinuxを無効
- 動作に必要なソフトウェアのインストール
- nginxのサービス登録と起動
- Giteaユーザの追加
timedatectl set-timezone Asia/Tokyo
sed -i -e 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
shutdown -r now
firewall-cmd --add-service=http --zone=public --permanent
firewall-cmd --reload
dnf install -y nginx
systemctl enable nginx
systemctl start nginx
useradd --user-group --shell /bin/bash --home-dir /opt/gitea gitea
Giteaの構築
- gitのインストール
- 必要なディレクトリを作成
- Giteaのバイナリを取得
- GiteaのServiceファイルを作成し、サービス登録と起動
- Nginx設定ファイルを作成し、サービス再起動
dnf install -y git
mkdir -p /opt/gitea/{gitea-repositories,data,custom,log}
curl -L https://dl.gitea.io/gitea/master/gitea-master-linux-amd64 -o /opt/gitea/gitea
chmod 755 /opt/gitea/gitea
chown -R gitea.gitea /opt/gitea
cat <<EOF > /usr/lib/systemd/system/gitea.service
[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
[Service]
RestartSec=2s
Type=simple
User=gitea
Group=gitea
WorkingDirectory=/opt/gitea/
ExecStart=/opt/gitea/gitea web --config /opt/gitea/custom/conf/app.ini -p 3000
Restart=always
Environment=USER=gitea HOME=/opt/gitea GITEA_WORK_DIR=/opt/gitea
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl start gitea
systemctl enable gitea
cat <<'EOF' > /etc/nginx/conf.d/gitea.conf
server {
listen 80;
server_name gitea.prosper2.net;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
EOF
systemctl restart nginx
Jenkinsの構築
- javaのインストール
- 必要なディレクトリを作成
- Jenkinsのwarファイルを取得し展開
- JenkinsのServiceファイルを作成し、サービス登録と起動
- Nginx設定ファイルを作成し、サービス再起動
dnf install -y java-11-openjdk-devel java-11-openjdk
mkdir -p /opt/jenkins/{data,web} /var/log/jenkins
curl -L https://updates.jenkins-ci.org/latest/jenkins.war -o /opt/jenkins/jenkins.war
cd /opt/jenkins/web
jar -xf /opt/jenkins/jenkins.war
chown -R gitea.gitea /opt/jenkins /var/log/jenkins
cat <<EOF > /usr/lib/systemd/system/jenkins.service
[Unit]
Description=Jenkins Automation Server
After=syslog.target network.target
[Service]
Type=simple
ExecStart=/usr/bin/java -jar /opt/jenkins/jenkins.war --webroot=/opt/jenkins/web --httpPort=8080 --logfile=/var/log/jenkins/jenkins.log
User=gitea
Restart=always
RestartSec=300s
Environment="JENKINS_HOME=/opt/jenkins/data"
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl start jenkins
systemctl enable jenkins
cat <<'EOF' > /etc/nginx/conf.d/jenkins.conf
server {
listen 80;
server_name jenkins.prosper2.net;
location / {
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Fix the "It appears that your reverse proxy set up is broken" error.
proxy_pass http://127.0.0.1:8080;
proxy_read_timeout 90;
proxy_redirect http://127.0.0.1:8080 jenkins.prosper2.net;
# Required for new HTTP-based CLI
proxy_http_version 1.1;
proxy_request_buffering off;
# workaround for https://issues.jenkins-ci.org/browse/JENKINS-45651
add_header 'X-SSH-Endpoint' 'jenkins.prosper2.net:50022' always;
}
}
EOF
systemctl restart nginx
各サーバの初期設定
Giteaのセットアップとユーザ追加
Giteaに設定したFQDNへブラウザでHTTPでアクセスする(今回の例では http://gitea.prosper2.net/ )
以下のように変更して、画面下部の「Giteaをインストール」をクリック
項目 | 値 |
---|---|
データベースのタイプ | SQLite3 |
SSHサーバーのドメイン | gitea.prosper2.net |
GiteaのベースURL | http://gitea.prosper2.net/ |
インストール後はこんな画面なので、
「アカウントが必要ですか? 今すぐ登録しましょう。」をクリック
組織を作成するので、画面右上の「+」のアイコンから「新しい組織」を選択して、、
Giteaの設定ファイルのチューニング
Giteaの設定ファイルは /opt/gitea/custom/conf/app.ini
にあります。
ここに以下を追加することで、リポジトリ内のファイルやIssueの検索ができるようになります。
----8<----(snip)----8<----
[indexer]
ISSUE_INDEXER_TYPE = bleve
ISSUE_INDEXER_PATH = indexers/issues.bleve
REPO_INDEXER_ENABLED = true
REPO_INDEXER_PATH = indexer/repos.bleve
UPDATE_BUFFER_LEN = 20
MAX_FILE_SIZE = 1048576
----8<----(snip)----8<----
ここに以下を追加することで、OpenID利用の停止と、ユーザが自由に登録することができなくなります。
(つまり、ユーザを追加するには、最初に作成したユーザで追加するようになります)
以下を変更する
----8<----(snip)----8<----
[openid]
;ENABLE_OPENID_SIGNIN = true
;ENABLE_OPENID_SIGNUP = true
ENABLE_OPENID_SIGNIN = false
ENABLE_OPENID_SIGNUP = false
[service]
;DISABLE_REGISTRATION = false
DISABLE_REGISTRATION = true
----8<----(snip)----8<----
こうしてからGiteaを再起動します。
systemctl restart gitea
Giteaに公開鍵を登録
もし自分の秘密鍵を生成していない場合には、ssh-keygen -t rsa -b 4096
などで生成しておく。
# cat ~/.ssh/id_rsa.pub
ssh-rsa AAAA(うんちゃらかんちゃら)7iQ== root@vmnetserv01.prosper2.net
これをコピーしておく。
SSH鍵の登録は、画面右上のアイコンから「設定」を選択して、、、
上部メニュのSSH/GPGキーから「キーを追加」を選択して、、、
GiteaにSlackのwebhookを登録する(組織単位)
ここでは特定の組織に対して共通のwebhookを設定します。
Slackアプリ管理画面( https://api.slack.com/apps )の「Create New App」をクリック
アプリ名と自分のワークスペースを選択して「Create App」する
Basic Information → Building Apps for Slack → Add features and functionality → Incoming Webhooks
を選択
作成したては「Off」になっているので、これを「On」にする
スクロールして下部の「Add New Webhook to Workspace」をクリック
Slackアプリから「App」を選択して、giteaがいるか確認する
以下のように追加されたら、curlコマンドをコピペしてターミナルから発行する。
組織のwebhookを設定するので、先ほど作成して「test_org」を表示させ、組織名の横にある歯車マークをクリック
設定メニュの「webhook」から「webhookを追加」の「Slack」をクリック
ターゲットURLに先ほど作成したSlackのURL(curlじゃないほう)を、チャネルにSlackアプリを追加したチャネル(#gitea)を設定し、最下部の「有効」にチェックをいれ、「webhookを追加」します。
(テストなので、すべてのイベント、すべてのブランチとしています)
これで、この組織に関連するイベントはすべてSlackに投稿されることになります。
ためしにリポジトリを作成するので、画面右上のプラスマークから「新しいリポジトリ」を選択
オーナーを「test_org」リポジトリ名を「sample」として作成する。(それ以外は空欄でOK)
これのような気もしますが、closeしてるしなぁ。。。
https://github.com/go-gitea/gitea/issues/9183
GiteaにJenkinsのwebhookを登録する
GiteaのイベントをJenkinsに通知するためのwebhookを設定します。Slackのwebhookとだいたい同じです。
組織の設定メニュからwebhookを追加します(webhookの種別は「gitea」を選択します。)
ターゲットURLは http://jenkins.prosper2.net/gitea-webhook/post のように設定し、最下部の「有効」にチェックをいれ、「webhookを追加」します。
(テストなので、すべてのイベント、すべてのブランチとしています)
Jenkinsのセットアップとユーザ追加
Jenkinsに設定したFQDNへブラウザでHTTPでアクセスする(今回の例では http://jenkins.prosper2.net/ )
初期パスワードはJenkinsをインストールしたサーバにあるので、これを入力
# cat /opt/jenkins/data/secrets/initialAdminPassword
b2546555f9184c4a8a28266b4cac7628
インターネット接続環境があれば、「Install Suggested plugins」を選ぶと勝手によさげなものをダウンロードしてくれる。
「Start Using Jenkins」をクリックすると完了
Jenkinsにプラグインを追加
ひとまず、以下のプラグインを導入しておく
- Gitea
- Global Slack Notifier
- Embeddable Build Status(ビルド状態のバッヂを追加する)
サイドメニュの「Jenkinsの管理」の「プラグインの管理」をクリック
「利用可能」タブを選択してプラグインを検索してインストールする。
こんな感じで Global Slack Notifier と Embeddable Build Status もインストールしておく。
Jenkinsのslackアプリを追加
Slackアプリ管理画面( https://api.slack.com/apps )の画面上部の検索窓に「jenkins」と入力して検索
「Jenkins CI」の部分がリンクになっているので、これをクリック
投稿先は先ほど作成した #gitea とし、「Jenkins CI インテグレーションの追加」をして、画面に従って設定していく。
Step3にある、「チームサブドメイン」と「インテグレーション用トークン認証情報 ID」があとで必要なので、控えておく
「Jenkinsの設定」→「システムの設定」の最下部にSlackがあるので、workspaceとチャネルを入力
(workspaceは先ほどの「チームサブドメイン」もしくは https://[workspace].slack.com/ のworkspaceの部分です)
クレデンシャル情報を追加するために、「追加」→「jenkins」を選択し、
「種類」を「Secret Text」にして、先ほどの「インテグレーション用トークン認証情報 ID」として登録する。
もとの設定画面でクレデンシャル情報を選択して、TestConnectionをクリックする。(成功するとSuccessと表示される)
TestConnectionが成功すると、Slackにはこんな通知がくる。
テストのため、ビルド結果のすべてをSlack通知するため、「Jenkinsの設定」→「システムの設定」の中央付近にGlobal Slack Messagesがのすべての欄にチェックをいれる。
Jenkinsのslackアプリが追加できない場合
なんらかの理由でSlackアプリが導入できない場合には、GiteaのSlack設定したときと同じような、
https://hooks.slack.com/services/(~~なんだかながいやつ~~)
を利用する。
「種類」を「Secret Text」にして、https://hooks.slack.com/services/(~~なんだかながいやつ~~)
の (~~なんだかながいやつ~~)
を登録する。
(スラッシュが入ったディレクトリ構造になってても、そっくりそのまま全部入れる。)
「高度な設定」をクリックして展開する。
「Override url」に https://hooks.slack.com/services/(~~なんだかながいやつ~~)
の https://hooks.slack.com/services/
を入力する。
もとの設定画面でクレデンシャル情報を選択して、TestConnectionをクリックする。(成功するとSuccessと表示される)
Gitea→Slackの通知とまざってよくわからん状態ですが、緑の部分がJenkins→Slack通知のビルド成功、赤の部分がJenkins→Slack通知のビルド失敗です。
Slackアプリでなくても動作できてますね。
以下のようなGroovyスクリプトを JENKINS_HOME/init.groovy.d/slack.groovy においてもよさそうです。
// https://javadoc.jenkins.io/plugin/slack/jenkins/plugins/slack/SlackNotifier.DescriptorImpl.html
import jenkins.model.Jenkins
import com.cloudbees.plugins.credentials.domains.Domain
import org.jenkinsci.plugins.plaincredentials.impl.*
import hudson.util.Secret
import com.cloudbees.plugins.credentials.CredentialsScope
def jenkins = Jenkins.getInstance()
def domain = Domain.global()
def store = jenkins.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()
def slackCredentialParameters = [
description: 'prosper2 slack credential',
id: 'slack-token',
secret: '(ここになんだか長いやつをいれる)'
]
def secretText = new StringCredentialsImpl(
CredentialsScope.GLOBAL,
slackCredentialParameters.id,
slackCredentialParameters.description,
Secret.fromString(slackCredentialParameters.secret)
)
store.addCredentials(domain, secretText)
def slack = jenkins.getDescriptorByType(jenkins.plugins.slack.SlackNotifier.DescriptorImpl.class)
//slack.setTeamDomain('')
slack.setTokenCredentialId(slackCredentialParameters.id)
slack.setRoom('#gitea')
slack.setBotUser(false)
slack.setSendAsText(false)
slack.setBaseUrl('https://hooks.slack.com/services/')
slack.save()
jenkins.save()
動作確認してみよう
README.mdをpushする
先ほど作成した test_org 配下の sample リポジトリに対して、以下のようにREADME.md
を作成してcommit -> pushする
Giteaではmermaid.jsが利用できるので、この書式を試してみます。
mkdir -p ~/gitea/sample
cd ~/gitea/sample
git init
cat <<'EOF' > README.md
# Markdown Rendering
## mermaid.js
```mermaid
graph TD;
A(stuff)-->B[one];
A-->C[two];
A-->D[three];
```
EOF
git add README.md
git commit -m "first commit"
git remote add origin gitea@gitea.prosper2.net:test_org/sample.git
git push -u origin master
リポジトリのpushを検知してJenkinsで自動ビルドさせる
JenkinsがGiteaにアクセスできるようにGiteaでトークンを発行しておく。
右上メニュの「設定」をクリックして、、、
上部メニュの「アプリケーション」でトークン名を入力して、「トークンを生成」する。
やべぇトークンがゲシュタルト崩壊してきた。
JenkinsにGiteaサーバを登録するので、「Jenkinsの管理」から「システムの設定」を選択
システム設定の中央付近にある「Gitea Servers」にURLを入力し、設定を保存する。
(きちんと認識されれば、Giteaのバージョン名が表示される。)
もしくは、Jenkinsの設定ファイルを直接編集します。
<?xml version='1.1' encoding='UTF-8'?>
<org.jenkinsci.plugin.gitea.servers.GiteaServers plugin="gitea@1.2.1">
<servers/>
</org.jenkinsci.plugin.gitea.servers.GiteaServers>
これを
<?xml version='1.1' encoding='UTF-8'?>
<org.jenkinsci.plugin.gitea.servers.GiteaServers plugin="gitea@1.2.1">
<servers>
<org.jenkinsci.plugin.gitea.servers.GiteaServer>
<displayName>test gitea server</displayName>
<serverUrl>https://www.prosper2.net/gitea</serverUrl>
<manageHooks>false</manageHooks>
</org.jenkinsci.plugin.gitea.servers.GiteaServer>
</servers>
</org.jenkinsci.plugin.gitea.servers.GiteaServers>
にします。
「Enter an item name」に適当な名称を入力し、「Gitea Organization」を選択してOKをクリック
上部メニュの「Projects」→「Gitea Organization」→「Creadential」→「追加」から先ほど作成したプロジェクト名を選択する
トークン入力画面が表示されるので、「種類」を「Gitea Personal Access Token」にして、先ほどGiteaで生成したトークンを「追加」する
Credentialとして追加したトークンを選択し、「Owner」には対象の組織名を入力する。で、保存。
すると勝手に組織内のリポジトリを走査してくれる。
現時点ではsampleというリポジトリが見つかり、そこにJenkinsfileがない、と言われている。
なので、このリポジトリにJenkinsfileをpushしてやればよい。
cd ~/gitea/sample
cat <<'EOF' > Jenkinsfile
pipeline {
agent any
stages {
stage('First Stage') {
steps {
echo "Build pipeline : First Stage"
}
}
}
}
EOF
git add Jenkinsfile
git commit -m "add Jenkinsfile"
git push -u origin master
すると、Slackに通知がくる
通知はGiteaにpushされたトリガーでGitea→Slackのwebhookがひとつ(のはずだが、また2通きてる。。。)
もうひとつは、GiteaにpushされたトリガーでGitea→Jenkinsのwebhookを叩いた結果、Jenkinsfileが見つかったので、自動的にビルドされ、その結果をJenkins→Slackのwebhookで通知したもの。
Jenkinsのビルド履歴にビルドされたログが表示されるので、右端にあるターミナルアイコンをクリックする。
こんな感じでビルド履歴がみれる。
今回は文字列表示のみなので、ログにその文字列が出力されている。
ビルド状態をGiteaのREADME.mdにバッヂで表示させてみる
画面上部のメニュのネスト表示があり、現在は「#1」のビルド履歴が表示されているが、「master」をクリックして、masterブランチの状態を表示させる。
このときのサイドメニュのEmbeddable Build Statusをクリックすると、、、
こんな感じでいろいろな書き方でバッヂステータスが表現できる。
ので、このなかからMarkdownを見つけて、README.mdに追記してpushしてみよう。
cd ~/gitea/sample
vi README.md
(以下を先頭に追記)
----8<----(snip)----8<----
[![Build Status](http://jenkins.prosper2.net/job/gitea%20build%20test/job/sample/job/master/badge/icon)](http://jenkins.prosper2.net/job/gitea%20build%20test/job/sample/job/master/)
----8<----(snip)----8<----
git add README.md
git commit -m "add Badge Status"
git push -u origin master
以下のようにJenkinsfileを修正して、ビルドが失敗するようにして、再度pushしてみる。
(echoで文字列を表示させる代わりに、シェルを走らせようとしている。この場合、そんなコマンドは存在しない。)
sed -i -e 's/echo/sh/' ./Jenkinsfile
git add Jenkinsfile
git commit -m "mod Jenkinsfile"
git push -u origin master
Giteaのステータスもfailingになっている。このバッヂ部分はJenkinsのビルドログへのリンクになっているので、クリックすると、、、
SlackにはGiteaにpushされた通知と、ビルドが失敗した通知がきている。
さいごに
テストとかデプロイの自動化とか、いろいろやりたいことがありすぎてなんなら自前でたてちまえ。ということで全部を足元においてみました。
AnsibleやDockerでも良かったのですが、クリーンインストール直後でもすぐ使えるように、コピペでまとめました。
それに公式のDockerfileならまだしも、個人が作成したDockerfileとかdocker-composeとかって、実は何してんだかよくわかんないなぁ、と思ったりもしました。ので、危険な処理してないよ。という意味も含めてです。
検証をやり直しながら進めたのでスクリーンショットの時系列が一部入れ替わってる部分があります。ごめんなさい。
さて、環境がつくれたので、つぎはテストコードを含めたホントのCIをやるぞぉー。
出典
https://docs.gitea.io/en-us/reverse-proxies/
https://wiki.jenkins.io/display/JENKINS/Jenkins+behind+an+nginx+reverse+proxy
https://www.eclipse.org/jetty/documentation/9.4.31.v20200723/startup-unix-service.html
https://gcube.wiki.gcube-system.org/gcube/Gitea/Jenkins:_Setting_up_Webhooks
https://plugins.jenkins.io/slack/