natural born minority
コンセプトは「近所のPC一つでできる継続的デプロイ」。
以下の感じでやりたいと思います。
Jenkins
と Nginx
と Docker
をインストールし、使うgit
に SpringBoot
製のアプリがpushされるごとに、Docker
内にbranch別アプリが立ち上がるhttp://[CIサーバIPorホスト名]/[アプリのコンテキスト]/[branch名]
雑い像はこんな感じです。
Amazon Linux
で動かします今回の”やること(やったこと)”は、すべてこのリポジトリに置いてあります。
のインストールを行います。
手動でやるなら、こんな感じ。(Amazon Linux前提)
# 必要なサービスと道具を一発インストール
sudo yum install docker nginx java-1.8.0-openjdk-headless git
# Jenkinsはリポジトリを足した後、インストール。
sudo sh -c "curl http://pkg.jenkins-ci.org/redhat/jenkins.repo > /etc/yum.repos.d/jenkins.repo"
sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
sudo yum install jenkins
sudo chmod +s /usr/bin/docker # ちょっと雑なことしてるけど、全ユーザから叩ける様に
この後、
docker ps
が叩けるかくらいの確認をしておいてください。
※上記のインストール作業をAnsilbeのplaybookとしてAsCodeこちらに置いてあります
ここからは、ブラウザを使った手動作業です。
Jenkins(標準リポジトリからインストールすれば2.0系になる)に、初期設定をしてください。
Jenkinsから「Nginxに対して任意の設定ファイルを置ける」ようにするため、/etc
の中のディレクトリの権限を変更します。
sudo chmod u+w /etc/nginx/default.d/
また、ファイルを配置した後「設定読みなおし」もJenkinsにさせたいので、 nginx
コマンドも、root
以外から叩けるようにしましょう。
sudo chmod +s /usr/sbin/nginx
※このやり方は少々”乱暴”です。クラウドなど「外からアクセス出来るトコ」でやるなら、groupで縛るなりもう少しセキュアにしてください
一旦、CIサーバ作成作業は、ここで終了です。
皆様が「CIにかけたいSpringBootプロダクト」を格納したgitリポジトリをご用意ください。
今回の記事中ではこちらのリポジトリを対象にします。
ローカルで ./gradlew bootRun
すると、こんな画面が出るアプリです。
当該のgitリポジトリ直下に Jenkinsfile
を作成します。
実際には、このリポジトリのJenkinsfileを持ってきて、一番上の APP_NAME
を好きな名前に変えればOKです。
中身を解説していきます。
stage('Jar Build') {
sh './gradlew build'
}
stage('Start Application in Docker container.') {
def branchName = getBranchName()
contextPath = '/' + APP_NAME + '/' + branchName
def localJarDir = '/var/tmp' + contextPath
containerName = APP_NAME + '_' + branchName
// 既存のコンテナがあれば削除
sh "docker rm -f ${containerName} || echo 'Container already exists. Delete container.'"
// あろうがなかろうが「Jarを置くディレクトリ」を再作成
sh "rm -rf ${localJarDir} && mkdir -p ${localJarDir}"
// 予め作っておいたJarを移動
sh "mv ./build/libs/*.jar ${localJarDir}/app.jar"
// dockerコンテナを生成して、SpringBootアプリを起動。
// その際、コンテキストパスを /[任意の名前]/[branch名] に変更
def cmd = "docker run -d --rm --name ${containerName} -v ${localJarDir}:/usr/src/myapp -w /usr/src/myapp ${JDK_DOCKER_IMAGE_NAME} java -jar ./app.jar --server.contextPath=${contextPath}"
sh cmd
}
ほぼコメントが語っていますが…
executable jar
としてビルド/var/tmp/[アプリの名前]/[branch名]
なディレクトリに移動docker container
を起動し、java
コマンドでjarを起動しています。
起動時に引数で「内部のコンテキストパスを変更している」のは、以前の記事でも触れたように「CSSなどファイル参照やリンクがおかしくなる」防止です。
この時点では、CIサーバ内で
http://172.17.0.x:8080/[アプリの名前]/[branch名]/
で内部でしかアクセス出来ないカタチ(172.17.0.x は「DockerコンテナのIP体系」)で立ち上がっています。
stage('Publish Application per branch by WebServer.') {
// Dockerのコンテナ名から「内部のIPアドレス」を割り出し。
def containerIp = getIpAddressByContainerName(containerName)
// Nginxの設定ファイルとして「内部のコンテナを末尾ポートを削除した状態」で外へ公開する。
sh "echo 'location ${contextPath} { proxy_pass http://${containerIp}:8080${contextPath}; }' > /etc/nginx/default.d/${containerName}.conf"
// Nginxの管理コマンドを叩いて「設定ファイルの読みなおし」をさせる。
sh 'nginx -s reload'
}
これまたコメントがほぼ解説ですが、
しています。
「Nginxの設定ファイル」を、echoにより一行で吐いてるため、わかりにくいですが…
/etc/nginx/default.d/[アプリ名]_[branch名].conf
というファイルを
location /[アプリ名]/[branch名] {
proxy_pass http://172.17.0.x:8080/[アプリの名前]/[branch名]/;
}
という内容で吐いています。
コレにより、
http://172.17.0.x:8080/[アプリの名前]/[branch名]/ (内部のみ参照出来るアドレス)
↓
http://[CIサーバIPorホスト名]/[アプリの名前]/[branch名] (CIサーバ自体のアドレス)
という外部公開をしています。
という Jenkinsfile
をリポジトリ直下に配置したら、commit&push をしておきます。
CIサーバに戻り、JenkinsにSpringBootアプリのgitリポジトリがpushされるごとに動く「Multibranch Pipeline」のジョブを作成します。
保存した直後に scan
が始まり、 master
branchの Jenkinsfile
を検出、その定義通りのパイプラインが実行されます。
パイプライン(Jenkinsfile
の内容)が、全て成功していれば、
http://[CIサーバIPorホスト名]/[アプリの名前]/[branch名]
で「 master
branchをjarビルド/デプロイしたアプリ画面」が閲覧できるはずです。
別branchを作り、少し変更しcommit&pushしてみます。
すると、先ほど作成したジョブに検知されて、ビルド/デプロイが走ります。
今回、branch名を another-branch-for-cd-test
としてpush、HTMLの一部を変更しているので、branch名をコンテキストパスに含むURLでアクセスすると…
期待通り「別branchのアプリがデプロイされた」のが見れますね。
これでbranchごとにpushタイミングでデプロイされるようになりましたが、branchが削除されてもDockerコンテナは削除されません。
このままではDockerコンテナが無尽蔵に生成され、CIサーバがパンクするでしょう。
そこで「削除されたbranchと対応するコンテナ(と設定ファイル)は削除する」ようにします。
「現在、アクティブなリモートbranch」を割り出し、それと 対を成さない
を、削除するような Jenkinsfile
を作成し、任意のタイミングで実行させます。
ここでは Jenkinsfile_maintenance と名付け、リポジトリの直下に置きました。
これを パイプライン
のジョブに登録し、一日一回くらい回しておけば、パンクすることは無いでしょう。
作りの問題で、いくつかの制約と課題があります。
/
はダメ、英数と _
-
はOK「アプリが単一でメチャクチャ軽い」「CIサーバの資源がある程度ある」というように、いろいろと極端な例のサンプルになっていますが、実際運用するときには、
docker swarm
使うとか)docker-compose
を検討と「アプリに応じて」の設計が必要だと思います。
また、「CIがJenkins依存」「1サーバに依存するのはどうか(引き剥がせない)」という議論があると思いますが、ここは割り切りで
「自身プロジェクト用の”CD専用のサーバ”とする(CI等とは別の枠組みでやる)」
という位置づけで”別立て”しといたら良いかな?と考えます。
恐らく(詳しくは調べていなけれど)「金の実弾をマシンガンすれば出来る」系のはなしだとは思います。
でも、仕事であれば「稟議が…」等で「継続的デプロイが遠いもの」になってしまうことがしばしばかと。
そうなるくらいなら「近所に落ちてるPCで」「なんなら自分のマシンで」気軽に出来たら…と思い書いてみました。
blog comments powered by Disqus