こんにちは、tahara です。
Docker を使って git push をトリガーにステージング環境をどんどんたてて開発しています。 いま見たら24面のステージング環境が動いていました。
新しいブランチを push すると Jenkins が Docker のコンテナを作りそこにデプロイしてくれます。 Jenknis では Git Pluign を使っています。 あとは shell script でガシガシとドロくさくやっています。
次がその shell script です。
#!/bin/bash export SSH_DOCKER="ssh user@docker.example.com" source ./config/jenkins/functions.sh # 20 は /etc/init.d/skype の XSERVERNUM=20 source `ls ~/.dbus/session-bus/*-20` export DBUS_SESSION_BUS_ADDRESS # bundler rvm_path=/home/user/.rvm /home/user/.rvm/bin/rvm-shell 'ruby-2.1.0' -c 'bundle install --path vendor/bundler' # https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin echo "GIT_COMMIT=$GIT_COMMIT" echo "GIT_BRANCH=$GIT_BRANCH" echo "GIT_PREVIOUS_COMMIT=$GIT_PREVIOUS_COMMIT" echo "GIT_URL=$GIT_URL" echo "GIT_AUTHOR_EMAIL=$GIT_AUTHOR_EMAIL" echo "GIT_COMMITTER_EMAIL=$GIT_COMMITTER_EMAIL" # まずいらないコンテナを削除する delete_unused_containers BRANCH=`echo $GIT_BRANCH | sed -e 's/^origin\///'` echo $BRANCH CONTAINER_NAME="d`echo $BRANCH | sed -e 's/^d\///;s/[-_\/].*//'`" echo "CONTAINER_NAME=$CONTAINER_NAME" HOST_NAME="${CONTAINER_NAME}.o.example.com" echo "HOST_NAME=${HOST_NAME}" if ${SSH_DOCKER} docker.io ps | grep "$CONTAINER_NAME *$" then echo "container is already exists." CONTAINER_ID=`${SSH_DOCKER} docker.io ps | grep "$CONTAINER_NAME *$" | awk '{print $1;}'` CONTAINER_IP=$(${SSH_DOCKER} "docker.io inspect --format='{{ .NetworkSettings.IPAddress }}{% end raw %}' ${CONTAINER_ID}") else echo "create container..." CONTAINER_ID=$(${SSH_DOCKER} docker.io run -d -t --name "${CONTAINER_NAME}" localhost:5000/outing) CONTAINER_IP=$(${SSH_DOCKER} "docker.io inspect --format='{% raw %}{{ .NetworkSettings.IPAddress }}' ${CONTAINER_ID}") ${SSH_DOCKER} ssh-keygen -f "/home/user/.ssh/known_hosts" -R ${CONTAINER_IP} echo "setup nginx..." sed -e "s/_server_name_/${CONTAINER_NAME}.o.example.com/;s/_container_ip_/${CONTAINER_IP}/" config/server/staging/docker/nginx-site.conf | ${SSH_DOCKER} tee /etc/nginx/conf.d/${CONTAINER_NAME}.conf ${SSH_DOCKER} sudo service nginx reload fi echo "CONTAINER_ID=$CONTAINER_ID" echo "CONTAINER_IP=$CONTAINER_IP" # cap rvm_path=/home/user/.rvm /home/user/.rvm/bin/rvm-shell 'ruby-2.1.0' -c "bundle exec cap docker deploy:migrations -s host_ip=$CONTAINER_IP -s host_name=${HOST_NAME} -s branch=$BRANCH" if [ $? -ne 0 ] ; then sbcl --script /var/lib/jenkins/skype/skype.lisp ";( ごめんなさい、エラーになっちゃいました。 https://ci.example.com/job/outing_docker_gitlab/ から確認してください。 (heidy)" exit 1 else sbcl --script /var/lib/jenkins/skype/skype.lisp "(ninja) Capistrano: いこーよを 確認(ステージング) 環境 http://${HOST_NAME}/ にデプロイしました (h)`git log --pretty='%n%s%n%b %an' HEAD...HEAD~ | head -n 10`" fi
Jenkins と Docker は別のマシンで動いているので ssh 経由でいろいろやっている感じです。
functions.sh
#!/bin/bash error_exit() { ssh deployer@tosa.actindi.net sbcl --script /var/lib/jenkins/skype/skype.lisp ";( ごめんなさい、エラーになっちゃいました。 https://ci.actindi.net/job/outing_master/ から確認してください。 (heidy)" exit 1 } # いらないコンテナを削除する delete_unused_containers() { for container_id in `${SSH_DOCKER} docker.io ps -q` do name=`${SSH_DOCKER} "docker.io inspect --format '{{ .Name }}' ${container_id}"` if echo ${name} | grep -q -E "^/d[0-9]+" then branch=`echo ${name} | sed -e 's|/d||'` if git branch -a --no-merged origin/master | grep -q "d/${branch}" then echo "${name} is alive." else echo "kill ${name} ${container_id}" ${SSH_DOCKER} docker.io kill ${container_id} ${SSH_DOCKER} docker.io rm ${container_id} ${SSH_DOCKER} rm -f /etc/nginx/conf.d/d${branch}.conf ${SSH_DOCKER} sudo service nginx reload fi fi done }
master にマージ済みのブランチのコンテナは自動で削除するようにしています。
あと Docker コンテナ内の nginx は外からは見えないので、 次のような nginx の設定ファイルをコンテナごとに自動で作っています。 そして DNS の設定で *.o.example.com を docker.example.com(Docker のホスト) の CNAME にしています。
# HTTP server server { listen 80; server_name _server_name_; location / { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/conf.d/outing-password; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header Host $host; proxy_redirect off; proxy_pass http://_container_ip_; } } # HTTPS server server { listen 443; server_name _server_name_; ssl on; ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; ssl_session_timeout 5m; ssl_protocols SSLv3 TLSv1; ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP; ssl_prefer_server_ciphers on; location / { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/conf.d/outing-password; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_redirect off; proxy_pass http://_container_ip_; } }
Docker のイメージは日次で本番の最新データをマスクしたもので作り直しているので、ステージングのデータは常に本番に近いものになります。
push するだけでブランチごとのステージングが作られるというのは思っていたより、ずっと快適なことでした。 DB のマイグレーションのあるタスクでも他タスクを気にせず作業できます。 以前は月2回の本番リリースもタスクごとにリリースできるようになりました。 とてもおすすめです。