rancherのバックエンドを外部コンテナでreplicaitonしてみた備忘録


[前回](https://qiita.com/smallpalace/items/5058a992d1420d523921)シングルでたててみてHAしたくなったのでそうした的なdocker初心者の備忘録です。

#### ・環境、前提など
Ubuntu16 が2台
overaynetworkをetcdで組んだ状態
一応mysqlもコンテナとして立ててmaster/slave構成にした
フロントは別途。

#### ・overraynetworkをetcdで組む
[ここ](https://qiita.com/smallpalace/private/9f41ac3ddcc95cfddb28)に書きました。
（今のところオーバレイにした意味があったのかあんまり認識できていない）

#### ・docker-compose.ymlを置く

以下の配置でdirectoryをつくりファイルを置いてく

```shell-session:master側
# tree . 
.
├── nginx-proxy
│   └── ssl
└── rancher-server
    ├── docker-compose.yml
    ├── init-sql-master
    │   └── init-master.sql
    ├── mycnf
    │   └── master
    │       └── custom-master.cnf
    ├── mysql

```

```shell-session:slave側
.
└── rancher-server
    ├── docker-compose.yml
    ├── init-sql-slave
    │   └── init-slave.sql
    ├── mycnf
    │   └── slave
    │       └── custom-slave.cnf
    └── mysql
```

```shell-session:directoryを作る
mkdir -p nginx-proxy/ssl
mkdir -p rancher-server/{mycnf/{slave,master},mysql,init-sql-master,init-sql-slave}
```

```yaml:master用docker-compose.yml
version: '2'
services:
  rancher-server:
    image: rancher/server:preview
    container_name: "rancher"
    depends_on:
      - mysql-master
    restart: unless-stopped
    command: --db-host ${DB_MASTER_HOSTNAME} --db-port 3306 --db-name ${DB_SCHEMA} --db-user root --db-pass ${MYSQL_ROOT_PASSWORD} --advertise-address ${MY_HOSTNAME}
    environment:
      VIRTUAL_PORT: 8080
      VIRTUAL_HOST: ${VIRTUAL_HOST}
      LETSENCRYPT_HOST: ${VIRTUAL_HOST}
      LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL}
    ports:
      - 8080:8080
      - 9345:9345
  mysql-master:
    image: mysql:latest
    container_name: "mysql-master"
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_SCHEMA}
    ports:
      - 3306:3306
    volumes:
      - '/home/docker/rancher-server/mysql:/var/lib/mysql:rw'
      - '/home/docker/rancher-server/mycnf/master:/etc/mysql/conf.d'
      - '/home/docker/rancher-server/init-sql-master:/docker-entrypoint-initdb.d'
  nginx-proxy:
    image: jwilder/nginx-proxy:latest
    container_name: "nginx-proxy"
    restart: always
    depends_on:
      - rancher-server
    ports:
     - "80:80"
     - "443:443"
    volumes:
      - '/home/docker/nginx-proxy/ssl:/etc/nginx/certs:ro'
      - '/etc/nginx/vhost.d'
      - '/usr/share/nginx/html'
      - '/var/run/docker.sock:/tmp/docker.sock:ro'
  letsencrypt-nginx-proxy-companion:
    image: jrcs/letsencrypt-nginx-proxy-companion:latest
    container_name: "letsencrypt"
    restart: always
    depends_on:
      - rancher-server
      - nginx-proxy
    volumes_from:
      - nginx-proxy
    volumes:
      - '/home/docker/nginx-proxy/ssl:/etc/nginx/certs:rw'
      - '/var/run/docker.sock:/var/run/docker.sock:ro'
networks:
  default:
    external:
      name: common_link
```

```yaml:slave用docker-compose.yml
version: '2'
services:
  rancher-server:
    image: rancher/server:preview
    container_name: "rancher"
    depends_on:
      - mysql-slave
    restart: unless-stopped
    command: --db-host ${DB_MASTER_HOSTNAME} --db-port 3306 --db-name ${DB_SCHEMA} --db-user root --db-pass ${MYSQL_ROOT_PASSWORD} --advertise-address ${MY_HOSTNAME}
    environment:
      VIRTUAL_PORT: 8080
      VIRTUAL_HOST: ${VIRTUAL_HOST}
        ports:
      - 8080:8080
      - 9345:9345
  mysql-slave:
    image: mysql:latest
    container_name: "mysql-slave"
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_SCHEMA}
    ports:
      - 3306:3306
    volumes:
      - '/home/docker/rancher-server/mysql:/var/lib/mysql:rw'
      - '/home/docker/rancher-server/mycnf/slave:/etc/mysql/conf.d'
      - '/home/docker/rancher-server/init-sql-slave:/docker-entrypoint-initdb.d'
networks:
  default:
    external:
      name: common_link
```

```conf:.envファイル
DB_MASTER_HOSTNAME= #masterのホスト名を指定
MY_HOSTNAME= #master,slaveそれぞれの値を指定
VIRTUAL_HOST= #rancherのurlにつかうnginxにproxyしてほしいかつ証明書がほしいFQDN
LETSENCRYPT_EMAIL= #letsencrypt用に指定するemail
MYSQL_ROOT_PASSWORD= #mysqlのrootパスワード
DB_SCHEMA=cattle #rancher用のDBスキーマ名
```


#### ・mysql用の設定ファイルと初期投入用SQLを置く
公式のコンテナ親切で解説を見た感じビルドしなくていいのかなと思いました。password部分は置換でどうぞ。

```sql:init-sql-master/init-master.sql
## create database for rancher
CREATE DATABASE IF NOT EXISTS cattle COLLATE='utf8_general_ci' CHARACTER SET='utf8';
## create user for rancher
GRANT ALL ON cattle.* TO 'cattle'@'%' IDENTIFIED BY 'password';
## create user for replication
CREATE USER IF NOT EXISTS 'repl'@'%';
SET PASSWORD FOR 'repl'@'%' = PASSWORD('password');
GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
```

```sql:init-sql-slave/init-slave.sql
## create user for replication
CREATE USER IF NOT EXISTS 'repl'@'%';
SET PASSWORD FOR 'repl'@'%' = PASSWORD('password');
GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
## change master for cattle
CHANGE MASTER TO MASTER_HOST='mymaster',MASTER_USER='repl',MASTER_PASSWORD='password',MASTER_AUTO_POSITION=1,MASTER_PORT=3306;
START SLAVE;
```

CREATE USERにIF NOT EXISTSつけてるのはbinlog_formatがROWでユーザがreplicationされて重複エラー起こして空コミット運用の手間がアレだからです。

```conf:my.cnf、master用
[mysqld]
wait_timeout = 600
## Memory Allocation per Connection
sort_buffer_size = 1M
join_buffer_size = 1M
max_allowed_packet = 16M
max_heap_table_size = 128M
tmp_table_size = 128M
table_definition_cache = 500
## for InnoDB param.
innodb_buffer_pool_size = 536870912
innodb_flush_method = O_DIRECT
innodb_io_capacity=400
innodb_io_capacity_max=3000
innodb_read_io_threads=6
innodb_write_io_threads=6
innodb_print_all_deadlocks=ON
## replication (master/slave)
server-id=1001 #unique
log-bin=mysql-bin
log_slave_updates=1
gtid-mode = ON
enforce_gtid_consistency=ON
master-info-repository=TABLE
relay-log-info-repository=TABLE
relay_log_recovery=ON
report_host=rancher01 #unique
report-port=3306
binlog_group_commit_sync_delay=1000
binlog_group_commit_sync_no_delay_count=128
binlog_format=ROW
## slow-log
slow_query_log=1
slow_query_log_file=/var/run/mysqld/mysql.slow
long_query_time = 1
expire_logs_days = 3
[mysqld_safe]
log_error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
[mysqldump]
quick
max_allowed_packet = 16M
[mysql]
no-auto-rehash
```

```conf:my.cnf、slave用
[mysqld]
wait_timeout = 600
## Memory Allocation per Connection
sort_buffer_size = 1M
join_buffer_size = 1M
max_allowed_packet = 16M
max_heap_table_size = 128M
tmp_table_size = 128M
table_definition_cache = 500
## for InnoDB param.
innodb_buffer_pool_size = 536870912
innodb_flush_method = O_DIRECT
innodb_io_capacity=400
innodb_io_capacity_max=3000
innodb_read_io_threads=6
innodb_write_io_threads=6
innodb_print_all_deadlocks=ON
## replication (master/slave)
server-id=1002 #unique
log-bin=mysql-bin
log_slave_updates=1
gtid-mode = ON
enforce_gtid_consistency=ON
master-info-repository=TABLE
relay-log-info-repository=TABLE
relay_log_recovery=ON
report_host=rancher02 #unique
report_port=3306
binlog_group_commit_sync_delay=1000
binlog_group_commit_sync_no_delay_count=128
binlog_format=ROW
## slow-log
slow_query_log=1
slow_query_log_file=/var/run/mysqld/mysql.slow
long_query_time = 1
expire_logs_days = 3
[mysqld_safe]
log_error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
[mysqldump]
quick
max_allowed_packet = 16M
[mysql]
no-auto-rehash
```
※server-idとreport_hostが一意になる値。my.cnfはデフォルトで使うとリソースを使い切れずにひどい目に合うから無駄にじっと見て変えることが多く結構デフォルト値見比べてみたけどgtid有効ならこんなもんかなと思いました。

|項目|認識してる概要など|
|:--|:--|
|wait_timeout|反応無しのタイムアウト時間。デフォたしか6時間くらいなので削減|
|sort_buffer_size|よく足りなくなるセッションバッファ|
|join_buffer_size|よく足りなくなるセッションバッファ|
|max_allowed_packet|送れる最大パケットサイズ。|
|max_heap_table_size|足らないとディスクI/Oで劇遅になるので増加|
|tmp_table_size|足らないとディスクI/Oで劇遅になるので増加|
|table_definition_cache|400 + (table_open_cache / 2)が適してるらしい|
|innodb_buffer_pool_size|オンメモリで運用するためにデータサイズ以上OS搭載メモリ75%未満を設定するやつ|
|innodb_flush_method|重複処理を省いて高速化するためにO_DIRECT指定|
|innodb_io_capacity|ディスクのIOPS性能を指定SSDやioDriveなら増やす|
|innodb_io_capacity_max|5.6は控えめがいいらしいが5.7は大丈夫|
|innodb_read_io_threads|ディスク読むスレッド数|
|innodb_write_io_threads|ディスクに書くスレッド数|
|innodb_print_all_deadlocks|デッドロック情報をshow innodb statusなどに出す|
|server-id|replicationするとき用のid、一意でないとrepできない|
|log-bin|バイナリログ出す意味とそのサフィックスを指定|
|log_slave_updates|スレーブにもマスタにもなれる指定|
|gtid-mode|gtid（グローバルトランザクションID）を有効化する|
|enforce_gtid_consistency|gtidのための一貫性のないSQLを禁じるやつ(WARN指定だと警告だけ出て実行はできるが非推奨）|
|master-info-repository|マスタ情報をtableに書くクラッシュセーフreplication用|
|relay-log-info-repository|スレーブ情報をtableに書くクラッシュセーフreplication用|
|relay_log_recovery|replicationの一貫性を保証するやつクラッシュセーフ用|
|report_host|masterに認識させる自分のホスト名やIPアドレスを指定|
|report_port|masterからshow slave hosts;で見るため用|
|binlog_group_commit_sync_delay|一括でコミットするとき待つマイクロ秒|
|binlog_group_commit_sync_no_delay_count|一括でコミットするとき待つクエリ数|
|binlog_format|バイナリログのフォーマット。ROWとMIXEDとSTATEMENTがありGTID有効だとROW推奨ぽいが遅延の噂も|
|slow_query_log=1|スロークエリログを出す|
|slow_query_log_file|スロークエリログの出力path|
|long_query_time|この秒数を超えるとスロークエリログに出る|
|expire_logs_days|バイナリログを何日たったものから消すか指定（ディスクfullに注意）|

ログとかは好きなとこに出せばいんじゃねと思いますがメモリの食い合いに注意というか[OS搭載メモリをセッションバッファ×接続数＋グローバルバッファ＋OS利用メモリが超えてはならないし](https://qiita.com/smallpalace/private/abbb7d24bdfc0d2eeedb)コンテナ相乗りを考慮すべきだしみたいなのがあると思います。あとはDisk性能に合わせた設定するのとTCP/IPの同時接続数限界的にDB入ってるコンテナに色々入れすぎた挙句フロントだけが増えたりする場合があるとアレかも。

https://yakst.com/ja/posts/3726

#### ・docker-compose up -dする

```
# docker-compose up -d
# docker-compose ps
        Name                 Command                 State                  Ports         
-----------------------------------------------------------------------------------------
letsencrypt            /bin/bash              Up                                          
                       /app/entrypoint. ...                                               
mysql-master           docker-entrypoint.sh   Up                     0.0.0.0:3306->3306/t 
                       mysqld                                        cp                   
nginx-proxy            /app/docker-           Up                     0.0.0.0:443->443/tcp 
                       entrypoint.sh  ...                            , 0.0.0.0:80->80/tcp 
rancher                /usr/bin/entry --db-   Up                     3306/tcp, 0.0.0.0:80 
                       host i ...                                    80->8080/tcp, 0.0.0. 
                                                                     0:9345->9345/tcp     
```
なんか途中で止まっててエラーっぽい時は-dとるとデーモンモードじゃなく標準出力にエラーでてわかりやすいかも。ただし抜けると止まってしまいますが。upの部分をstopにすると停止、downだと削除、restartで再起動、など。

#### ・ハマったとこと思ったこと
→初期投入用SQL間違うと色々はまりますので実際打って確かめたほうがいいかも。調査の過程でdocker attachとdocker execの違いを知りました。
→rancherはpreviewだと2.0が入るけど2.0はpreviewなだけあってアクセスコントロールの設定が一切できない仕様でした。その状態で放置するのもどうかと思って1.6に戻しました。あと環境をk8sにするとカタログが少ない気がしました。
→跡形もなく消してやりなおす方法がちょっとよくわからなかったのですがk8s用環境つくると`docker rm -f $(docker ps -aq)`するだけだとOS再起動しないとiptablesの設定が残ってキモい感じでした。
→kubernetesアンチパターン的な勉強会の情報で、コンテナでRDBMS扱うの自体がアンチっぽいような話を人づてに聞いた気がするし分散クラスタに向いてる作りでないのでKVS的な実装に寄せてCloud Spanner使うみたいなのがお金がかかるけどバックエンドRDBMSをスケーラブルに寄せられる方法なのかなあ、グーグルレベルに大規模で困ってて予算が潤沢じゃなかったらバックエンドRDBMSコンテナ化してスケーラブルに自動制御というのは難しそう。もしくはさいきんでたAuroraServerlessとかがいいんですかね？
→mysqlfailoverはadminサーバ的なのがもう一台増えたら考えることにしようかと。bkup実装も別途。
→ディスク容量とバイナリログの管理に気を付けないと、しばらくたつとディスクfull→相乗りのetcd停止でオーバレイネットワーク消える→関連サービスもろとも全部止まる、というコンボをくらうので注意が要るなあ、と。

#### ・参考
mysqlコンテナ周り
[ライブラリ/ mysql - Docker Hub](https://hub.docker.com/_/mysql/)
[Dockerの公式MySQLイメージの使い方を徹底的に解説するよ · DQNEO起業日記](http://dqn.sakusakutto.jp/2015/10/docker_mysqld_tutorial.html)

dockerまわり
[docker-compose.ymlが環境別に複数ある場合はCOMPOSE_FILEを定義しておくと幸せになれる](https://suin.io/535)
[docker-compose.ymlの中で環境変数を展開する - Qiita](https://qiita.com/friedaji/items/c1894821a2c49395cfd7)
[Environment variables in Compose | Docker Documentation](https://docs.docker.com/compose/environment-variables/#the-env-file)

rancherまわり
[Installing Rancher Server](http://rancher.com/docs/rancher/v1.6/en/installing-rancher/installing-server/#multi-nodes)