2021年4月24日土曜日

[Swarm] docker stack deploy で Blue-Green deployment の方法を考える

[Swarm] docker stack deploy で Blue-Green deployment の方法を考える

概要

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 アプリ

  • bundle init
  • vim Gemfile
gem "thin"
gem "rake"
gem "mysql2"
gem "sinatra"
gem "sinatra-contrib"
gem "sinatra-activerecord"
  • bundle install

アプリは Blue or Green のどちらに振り分けられているかわかるようにしておきます
また DB は手動で作成するのでマイグレーションファイルは作成しません

  • vim app.rb
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
  • vim config.ru
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

  • vim Dockerfile
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 ファイルは外します

  • vim .dockerignore
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) にバランシングするようにします

  • vim default.conf
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 のアプリからレスポンスが返ってきます

  • curl localhost/all

=> {"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 デプロイメントに含めるコンポーネントはステートレスなコンポーネントにする

かなと思います

0 件のコメント:

コメントを投稿