たった1日で基本が身に付く! Docker/Kubernetes超入門 実践 CHAPTER6
SECTION01
SCMとパイプラインを使ったCI/CDの実現
読むだけです。
CI/CDではDockerを多用できる
読むだけです。
Jenkinsについて知ろう
読むだけです。
Jenkinsfileを使ったパイプラインの作り方を知ろう
読むだけです。
SECTION02
環境の全体構成を把握しよう
読むだけです。
Dockerホスト上にJenkinsを用意しよう
ホストは、Chapter2で作成したHost-A、Host-B、Host-Cを使いましょう。
Host-Aを開きます。
Host-Aでdocker composeしましょう。
DockerfileがCentOS用なので、Ubuntu用に・・・と思ったのですが、Ubuntu用は難易度が高いそうなので、Debian用に書き換えましょう。
FROM jenkins/jenkins:lts-jdk11
USER root
# 必要なパッケージと Docker CLI のインストール
RUN apt-get update && apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release \
&& curl -fsSL https://download.docker.com/linux/debian/gpg \
| gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
https://download.docker.com/linux/debian bookworm stable" \
> /etc/apt/sources.list.d/docker.list \
&& apt-get update \
&& apt-get install -y docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*
# Docker Compose と kubectl のインストール
RUN curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose \
&& chmod +x /usr/local/bin/docker-compose \
&& curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/linux/amd64/kubectl \
&& mv kubectl /usr/local/bin/kubectl \
&& chmod +x /usr/local/bin/kubectl
# 秘密鍵とスクリプトの配置
COPY --chown=jenkins:jenkins ./id_rsa /tmp/id_rsa
COPY --chown=jenkins:jenkins ./id_rsa.pub /tmp/id_rsa.pub
COPY ./jenkins.sh /usr/local/bin/jenkins.sh
RUN chmod 0600 /tmp/id_rsa /tmp/id_rsa.pub \
&& chmod 0775 /usr/local/bin/jenkins.sh
USER jenkins
ENV JAVA_OPTS -Djava.awt.headless=true -Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Tokyo -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8
EXPOSE 8080 50000
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/jenkins.sh"]
本にしたがって、Jenkinsを設定しましょう。
その後、本の中で、開発環境と本番環境にSSH接続を、と出てきます。
これは、CHAPTER2で用意していた、Host-BとHost-Cを使いましょう。
CHAPTER2のAnsibleを使い、JenkinsからHost-BとHost-Cに接続できるようにします。
※Ansibleがなかなか動かないので、手動で進めていきます。
CHAPTER2の手順に従って進めましょう。
ただ、使うファイルをjenkinsコンテナにコピーしておく必要があります。
# docker cp /mnt/c/GitHub/docker-book/chap2/ansible/dockerhost jenkins:/tmp
jenkinsコンテナには以下でログインできます。
# docker exec -it --user root jenkins bash
CHAPTER2のページを見て、sshのインストールなどをしてきてください。ansibleの実行は不要です。
SSHのポートは、念のため、2228にしておきましょう。
その後、cdしておきましょう。
# cd /tmp/dockerhost
鍵ファイルのコピーを忘れがちです。
# cp id_rsa ~/.ssh
# cp id_rsa.pub ~/.ssh
鍵ファイルをssh-copy-idします。
# ssh-copy-id -p 2228 root@localhost
下記の追記が必要でした。
# vi ~/.ssh/config
Host jenkins
HostName localhost
Port 2228
User root
jenkinsコンテナから見たHost-B、Host-CのIPアドレスを調べます。
が、ipコマンドがないので、まず、インストールします。
# apt-get update
# apt-get install -y iproute2
jenkinsコンテナから見たホストHost-AのIPを確認します。
※Host-B、Host-Cも同じIPアドレスです。
# ip route | grep default
default via 172.19.0.1 dev eth0
jenkinsコンテナからHost-B、Host-Cへの接続確認をします。
# ssh root@172.19.0.1 -p 2226
# ssh root@172.19.0.1 -p 2227
もし、Connection refusedが出たら、sshdが起動してないかもしれないので、そのホストに行って、sshdを起動します。
Host-B
# sudo /usr/sbin/sshd -p 2226
Host-C
# sudo /usr/sbin/sshd -p 2227
sshをホスト名でできるようにするため、configの設定を見直します。
# vi ~/.ssh/config
Host Host-A
HostName 172.19.0.1
Port 2225
User root
Host Host-B
HostName 172.19.0.1
Port 2226
User root
Host Host-C
HostName 172.19.0.1
Port 2227
User root
Host jenkins
HostName localhost
Port 2228
User root
下記でsshできるようになります。
# ssh Host-A
# ssh Host-B
# ssh Host-C
ただ、docker containerコマンドでsshするときは、下記のようにする必要があります。
# docker container exec -it jenkins ssh root@172.19.0.1 -p 2226
# docker container exec -it jenkins ssh root@172.19.0.1 -p 2227
下記は失敗します。
# docker container exec -it jenkins ssh Host-B
# docker container exec -it jenkins ssh Host-C
せっかく設定したのに悲しいですが、これはなぜかというと、名前解決は恐らくjenkinsコンテナのホストHost-Aの設定を見に行きます。
Host-Aの設定では、localhostにしています。
jenkinsコンテナから見た場合は、172.19.0.1です。
この差により、エラーになります。
もちろん、Host-Aの設定も、172.19.0.1にすれば、通るはずです。
本にある通り、DockerHubへのログインもしておきます。
※本のコマンドに、不要な文字列”ssh”があるので、削除しましょう。
# docker container exec -it jenkins docker login
さあ、ようやく準備が終わりました。
GitHubリポジトリを作成しよう
本にはdocker-kvsをコピーし、とあるが、どれか分かりにくいですね。
ダウンロードしたサンプルファイルの、\chap6\c6cicdのことのようです。
CHAPTER4で使ったdocker-kvsフォルダの中にファイルが2つあるので、それらを削除します。
\chap6\c6cicdの中身を、docker-kvsフォルダの中にコピーし、Commit/Pushし、GitHub上で確認しておきましょう。
Jenkinsfileを確認しよう
本にある通り、環境変数は、自分の環境に合わせて書き換えましょう。
筆者の場合は、下記のようになります。
DOCKERHUB_USER = "rasken2003"
BUILD_HOST = "root@172.19.0.1:2226"
PROD_HOST = "root@172.19.0.1:2227"
Jenkinsのジョブを作成しよう
本にしたがって進めましょう。
スケジュール欄ですが、「*****」ではなく「* * * * *」(間に半角スペース)です。
この設定で、毎分起動するようになります。
さらに、ビルドするブランチは、「master」ではなく、「main」に変更しましょう。
これは、GitHubのデフォルトが変わったためです。
SECTION03
手動ビルドでパイプラインを実行してみる
きっと何かしら失敗するでしょう。
失敗したら、永続リンクにあるリンクをクリックし、左メニューからPipeline Consoleを開きましょう。
見やすく表示されていると思います。
※実行中でも、左下のところから表示できます。
筆者の場合、編集したJenkinsfileをCommit/Pushしていなかったため、変数が古いまま実行したため、エラーになりました。
が、さらにエラーが出ました。
Jenkinsfileで、docker-composeを使っていて、古いからダメだそうです。
docker composeに変えました。
以下の通りです。
忘れずに、コミット&プッシュしましょう。
※次のところで出る、ポート問題の対応も済んでいます。
pipeline {
agent any
environment {
DOCKERHUB_USER = "rasken2003"
BUILD_HOST = "root@172.19.0.1:2226"
PROD_HOST = "root@172.19.0.1:2227"
BUILD_TIMESTAMP = sh(script: "date +%Y%m%d-%H%M%S", returnStdout: true).trim()
}
stages {
stage('Pre Check') {
steps {
sh "test -f ~/.docker/config.json"
sh "cat ~/.docker/config.json | grep docker.io"
}
}
stage('Build') {
steps {
sh "cat docker-compose.build.yml"
sh "DOCKER_HOST=ssh://${BUILD_HOST} docker compose -f docker-compose.build.yml down"
sh "docker -H ssh://${BUILD_HOST} volume prune -f"
sh "DOCKER_HOST=ssh://${BUILD_HOST} docker compose -f docker-compose.build.yml build"
sh "DOCKER_HOST=ssh://${BUILD_HOST} docker compose -f docker-compose.build.yml up -d"
sh "DOCKER_HOST=ssh://${BUILD_HOST} docker compose -f docker-compose.build.yml ps"
}
}
stage('Test') {
steps {
sh "docker -H ssh://${BUILD_HOST} container exec dockerkvs_apptest pytest -v test_app.py"
sh "docker -H ssh://${BUILD_HOST} container exec dockerkvs_webtest pytest -v test_static.py"
sh "docker -H ssh://${BUILD_HOST} container exec dockerkvs_webtest pytest -v test_selenium.py"
sh "DOCKER_HOST=ssh://${BUILD_HOST} docker compose -f docker-compose.build.yml down"
}
}
stage('Register') {
steps {
sh "docker -H ssh://${BUILD_HOST} tag dockerkvs_web ${DOCKERHUB_USER}/dockerkvs_web:${BUILD_TIMESTAMP}"
sh "docker -H ssh://${BUILD_HOST} tag dockerkvs_app ${DOCKERHUB_USER}/dockerkvs_app:${BUILD_TIMESTAMP}"
sh "docker -H ssh://${BUILD_HOST} push ${DOCKERHUB_USER}/dockerkvs_web:${BUILD_TIMESTAMP}"
sh "docker -H ssh://${BUILD_HOST} push ${DOCKERHUB_USER}/dockerkvs_app:${BUILD_TIMESTAMP}"
}
}
stage('Deploy') {
steps {
sh "cat docker-compose.prod.yml"
sh "echo 'DOCKERHUB_USER=${DOCKERHUB_USER}' > .env"
sh "echo 'BUILD_TIMESTAMP=${BUILD_TIMESTAMP}' >> .env"
sh "echo 'WEB_PORT=8082' >> .env"
sh "cat .env"
sh "DOCKER_HOST=ssh://${PROD_HOST} docker compose -f docker-compose.prod.yml up -d"
sh "DOCKER_HOST=ssh://${PROD_HOST} docker compose -f docker-compose.prod.yml ps"
}
}
}
}
でもまた、エラーが出ました。
Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint dockerkvs_web (f7034784af6bf8ab1acee4f29721691b8151f492e8f9d78293c09f9040ff2232): failed to bind host port for 0.0.0.0:80:172.20.0.6:80/tcp: address already in use
ポートが競合してるってことです。
本来なら、サーバーは別に存在するから、こういうことに苦労しないのでしょうが、お試しで1つの環境でやっているから、仕方ないですね。
docker-compose-build.ymlを下記のように書き換えます。
80:80
とあるところを、
8081:80
にします。
docker-compose.prod.ymlも書き換えます。
80:80
とあるところを、
"${WEB_PORT:-8082}:80"
にします。
※環境変数は、Jenkinsfileで設定しています。
さあ、コミット&プッシュして、ジョブ実行です。
ようやく動き・・・ダメでした。
テストのところで、appに接続失敗しています。
これはかなりつまずきました。
wait-for-it.shというものを入れて、ウェイトをしてみました。
これは、appが立ち上がる前に、apptestが動いてしまっているのでは、という疑いがあるためです。
ダウンロードします。
# cd /mnt/c/github/docker-kvs
# curl -o wait-for-it.sh https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh
# chmod +x wait-for-it.sh
上記の対応は無意味でした。appは立ち上がっていませんでした。
本当の原因は、またも、Flaskのバージョンが古い問題でした。
これがなぜかJenkinsのログに出てこないのです。
どうも、Flaskのバージョンが古いゆえ、らしいですが、本当のところは不明です。
/docker-kvs/app/Dockerfile
1.1.1になっているので、2.2.3に書き換えてください。
Jenkinsfileは対応済みのものをすでに用意していたので、このまま続行してください。
最後に、DockerHubへのPushでエラーが。
なんと、無料範囲を超えているとか。
2024年あたりから、プライベートは1個までになったそうです。
パブリックに変えればいいようです。
やっと、成功しました。
アプリを動作確認したいところですが、本にしたがって、先に進みます。
ビルドの履歴を確認してみる
これはもう、何度もやっているでしょうから、いいですね。
読むだけです。
SCMを更新して自動でパイプラインを実行させよう
タイトルが「DockerKVS Service」になっていると書いてありますが、すでに「Hello Service」に変更されています。
なんでもいいので、例えば、「Hello Service2」にしておきましょう。
筆者はテスト1回目は失敗しました。
設定ミスをしていました。(GitのURLの最後の/がなかったため)
エラーの確認は、Gitのポーリングログや設定でできます。
Jenkinsが、バージョン古いかのようなメッセージを出し続けていたので、最新にしました。
\chap6\c6jenkins\Dockerfile
FROM jenkins/jenkins:jdk21
その後、何度やっても、エラーが出てしまいましたが、コンテナの重複エラーで、きちんとコンテナを削除していなかったためです。
各ホストで、
# docker container prune
を実行しましょう。
これでようやく・・・と思ったのですが、下記のエラーが出ました。
前は出なかったと思うんですけどね。
+ DOCKER_HOST=ssh://root@172.19.0.1:2227 docker compose -f docker-compose.prod.yml up -d
time="2025-04-28T05:02:34Z" level=warning msg="/var/jenkins_home/workspace/docker-kvs@2/docker-compose.prod.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion"
unable to get image 'redis:5.0.6-alpine3.10': error during connect: Get "http://docker.example.com/v1.49/images/redis:5.0.6-alpine3.10/json": command [ssh -o ConnectTimeout=30 -T -l root -p 2227 -- 172.19.0.1 docker system dial-stdio] has exited with exit status 255, make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=ssh: connect to host 172.19.0.1 port 2227: Connection refused
script returned exit code 1
これは、単純に、sshが起動していなかっただけでした。
# sudo mkdir -p /run/sshd
# /usr/sbin/sshd -p 2227
あと、1分おきだと早すぎる(ビルドに3分くらいかかるので、どうしても重複してしまう)ので、5分に1回に変更しました。
*/5 * * * *
ようやく自動実行が成功しました。
Jenkinsfileの処理を見てみよう
大事な内容ですが、読むだけです。
パイプライン処理1:変数の定義
大事な内容ですが、読むだけです。
パイプライン処理2:環境などの事前チェック
大事な内容ですが、読むだけです。
パイプライン処理3:ビルド環境でイメージを作成
大事な内容ですが、読むだけです。
パイプライン処理4:ビルド環境でユニットテストを実行
大事な内容ですが、読むだけです。
パイプライン処理5:イメージをレジストリに登録
大事な内容ですが、読むだけです。
パイプライン処理6:本番環境で本番用イメージを自動展開
大事な内容ですが、読むだけです。
CHAPTER6は以上になります。
ピンバック: たった1日で基本が身に付く! Docker/Kubernetes超入門 実践 CHAPTER5 – ふうがITビジネス