概要
docker swarm 上で Blue-Green デプロイを実現する方法を考えみました
stack deploy を使ってアプリを 2 つ用意しそれを LB などを使って切り替える方法を考えます
今回は 2 ノードの Swarm 環境 (192.168.100.10, 192.168.100.11) で試します
Swarm 環境はローカルではなくリモート先にあるものとします
環境
- Ubuntu 18.04
- docker 19.03.13
- Ruby 3.0.0
- MySQL 8.0.24
テスト用のアプリ作成
分かりやすいようにデータベースを使った Web アプリを作成します
データベースはそのままでアプリのみを Blue-Green デプロイメントで切り替えます
Web アプリ
gem "thin"
gem "rake"
gem "mysql2"
gem "sinatra"
gem "sinatra-contrib"
gem "sinatra-activerecord"
アプリは Blue or Green のどちらに振り分けられているかわかるようにしておきます
また DB は手動で作成するのでマイグレーションファイルは作成しません
require 'sinatra'
require 'sinatra/json'
require "sinatra/activerecord"
require './models/user.rb'
class DBTestApp < Sinatra::Base
register Sinatra::ActiveRecordExtension
get '/all' do
@users = User.all
deployment = ENV["DEPLOYMENT"] || "blue"
ret = {
deployment => @users
}
json ret
end
end
require './app'
run DBTestApp
database.yml
- mkdir config
- vim config/database.yml
development:
adapter: mysql2
database: test
host: 192.168.100.200
username: root
password: xxxxxx
encoding: utf8
データベースの準備
データベースは Swarm とは別の環境 (192.168.100.200) に配置してあることを想定しています
以下はコンテナで動作させていますがコンテナでなくても構いません
mkdir initdb.d
vim initdb.d/test.sql
DROP SCHEMA IF EXISTS test;
CREATE SCHEMA test;
USE test;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT(10) NOT NULL AUTO_INCREMENT,
name VARCHAR(40),
age INT(10),
PRIMARY KEY (id)
);
INSERT INTO users (name, age) VALUES ("hawk", 10);
INSERT INTO users (name, age) VALUES ("snowlog", 20);
docker run -d -v $(pwd)/initdb.d:/docker-entrypoint-initdb.d -e MYSQL_ROOT_PASSWORD=xxxxxx -p 3306:3306 --name db mysql:latest --default-authentication-plugin=mysql_native_password
mysql -h 192.168.100.200 -u root -p
こんな感じでアクセスしてデータがあれば OK です
--default-authentication-plugin=mysql_native_password
は MySQL8 なので設定しています
これを設定しないと以下のエラーが発生します
「ActiveRecord::ConnectionNotEstablished - Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory
」
docker-compose.yml
FROM ruby:3
WORKDIR /app
ADD . /app
RUN bundle config path vendor
RUN bundle install
EXPOSE 9292
CMD ["bundle", "exec", "rackup", "config.ru", "-o", "0.0.0.0"]
dockerignore を用意しないと開発サーバでビルドした mysql2 の shared object ファイルが配置されてしまいエラーになるので vendor ファイルは外します
vendor/
Blue 用と Green 用を作成します
デプロイするコンテナは各ノードに配置されるようにします
vim docker-compose-blue.yml
version: "3.8"
services:
web:
image: registry:5000/root/web:latest
ports:
- "9292:9292"
environment:
DEPLOYMENT: blue
deploy:
mode: replicated
replicas: 2
placement:
max_replicas_per_node: 1
- vim docker-compose-green.yml
version: "3.8"
services:
web:
image: registry:5000/root/web:latest
ports:
- "9291:9292"
environment:
DEPLOYMENT: green
deploy:
mode: replicated
replicas: 2
placement:
max_replicas_per_node: 1
ポイントは Green 側の LISTEN ポートは 9291 にする点です
こうすることで同一の docker swarm 上に Blue と Green の両方のアプリをデプロイできるようにします
イメージについて
今回はリモートの docker swarm 環境を使うのでイメージをビルドして事前にプライベートリポジトリに push しています
もしプライベートリポジトリがない場合は各 swarm のノードにログインしてそれぞれでビルドしても OK です
LB の準備
今回は nginx を使います
またコンテナで動作させます
ここはクラウドロードバランサや haproxy など代用できるものなら何でも OK です
ポイントはポートでバランシング先を切り替える点です
とりあえず Blue (9292) にバランシングするようにします
upstream web {
server 192.168.100.10:9292;
server 192.168.100.11:9292;
}
server {
listen 80;
location / {
proxy_pass http://web;
}
}
動作確認
まずは Blue と Green のアプリをデプロイします
ノードに直接アクセスして両方のアプリが動作していることを確認します
docker -H 192.168.100.10:2376 stack deploy blue -c docker-compose-blue.yml --with-registry-auth
docker -H 192.168.100.10:2376 stack deploy green -c docker-compose-green.yml --with-registry-auth
次に LB となる nginx を起動します
docker run -d -p 80:80 -v $(pwd)/default.conf:/etc/nginx/conf.d/default.conf --name web nginx
これで nginx にアクセスすると blue のアプリからレスポンスが返ってきます
=> {"blue":[{"id":1,"name":"hawk","age":10},{"id":2,"name":"snowlog","age":20}]}
次に green のアプリ (9291) に切り替えてみます
upstream web {
server 192.168.100.10:9291;
server 192.168.100.11:9291;
}
server {
listen 80;
location / {
proxy_pass http://web;
}
}
docker restart web
curl localhost/all
=> {"green":[{"id":1,"name":"hawk","age":10},{"id":2,"name":"snowlog","age":20}]}
このような感じで LB となっているコンポーネントで振り先変更するだけでアプリのデプロイを完了することができます
最後に
今回は 1 つの Swarm 環境で Blue-Green デプロイメントする方法を紹介しましたが Swarm 環境を複数用意しても OK です
その場合は LB 側でバランシングする際にポートバランシングだけではなく IP や FQDN などで別の Swarm に向けるようにしてください
ポイントは
- ステートフルなコンポーネントは外部に配置する
- Blue-Green デプロイメントに含めるコンポーネントはステートレスなコンポーネントにする
かなと思います