ในปี 2018 ใครๆก็พูดถึง Kubernetes กัน หลายๆที่ ใช้กันเป็นปกติ เราก็ใช้ที่ DomeCloud แต่ในบางงาน ที่รันอยู่มานานแล้ว ไม่ได้ย้ายไปอยู่บน Kubernetes แต่ก็ยังรันบน Docker อยู่ดี มีหลายๆเซอร์วิส ที่ยังคงทำงานได้ดีอยู่ จนกระทั่งเจอพญานาค

ไม่ว่าพญานาคที่ว่า จะมาจากไหน ช่างมัน วันนี้ ผมจะมาเล่าถึงการทำ MariaDB Cluster ด้วย Galera

Mariadb-seal-browntext.svg

galera_cluster_logo

บล็อกนี้ ผมจะมาเล่าถึงการทำ MariaDB Galera Cluster เพื่อแบ่งปันกันครับ ใครมีข้อแนะนำอะไร ส่งข้อความมาให้ผมแก้ได้เลยที่ https://m.me/koonnarate

เริ่มเลยดีกว่า

ตัว MariaDB รองรับการทำ Galera Cluster ในตัว ตั้งแต่เวอร์ชั่น 10.1 (Changes & Improvements in MariaDB 10.1)

ในบล็อกนี้ ผมจะทำตัวอย่าง การทำ Cluster ด้วย MariaDB 10.2 บน Docker แต่จะเป็น Docker บนเครื่องเดียวกัน "ไม่สามารถ เอาไปใช้งานจริงได้" ถ้าจะเอาไปใช้งานจริง ต้องแยกให้แต่ละโหนด อยู่คนละเครื่องเซิฟเวอร์ จะใช้ผ่าน Docker network macvlan หรือ จะใช้ Default bridge network แล้วรันด้วยการ map port ก็ได้เช่นเดียวกัน แต่ ควรที่จะรันให้อยู่ภายใต้ Private Network

ถ้าใครจะใช้การ map port ต้อง map port ตามนี้

  • 3306
  • 4567
  • 4568
  • 4444

แต่ละ Port คืออะไรบ้าง ตามไปดู Galera Cluster FIREWALL SETTINGS

ย้ำ บล็อกนี้ เทสให้ดูบน Docker ที่รันอยู่บนเครื่องเดียวกัน

แรกสุด สร้าง Network สำหรับ Docker ใช้ Bridge Driver และใส่ subnet เป็น 192.168.10.0/16 ชื่อ db-net

docker network create -d bridge \
    --subnet=192.168.10.0/16 \
    db-net

จะสร้าง Database 3 containers ชื่อ node0, node1, node2 โดยจะมี Galera config แบบนี้ สำหรับ node0

[mysqld]
binlog_format=ROW
default-storage-engine=innodb
innodb_autoinc_lock_mode=2
bind-address=0.0.0.0

# Galera Provider Configuration
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so

# Galera Cluster Configuration
wsrep_cluster_name="galera_cluster"
wsrep_cluster_address="gcomm://192.168.10.10,192.168.10.11,192.168.10.12"

# Galera Synchronization Configuration
wsrep_sst_method=rsync

# Galera Node Configuration
wsrep_node_address="192.168.10.10"
wsrep_node_name="node0"

สิ่งที่โหนดอื่นๆ จะต่างกัน จะมีส่วนนี้

# Galera Node Configuration
wsrep_node_address="192.168.10.10"
wsrep_node_name="node0"

นอกนั้นคอนฟิกเหมือนกัน

  • wsrep_cluster_address="gcomm://" คือ IP ของโหนดที่จะเข้ามา Join Cluster คั่นด้วย comma
  • wsrep_node_address คือ IP ของโหนดนั้นๆ
  • wsrep_node_name คือ ชื่อของโหนด

ในคอนฟิก ผมมี IP 3 IP คือ

  • 192.168.10.10 [node0]
  • 192.168.10.11 [node1]
  • 192.168.10.12 [node2]

รัน node0 ด้วย docker

docker run --rm -it --name node0 \
    --net=db-net \
    --ip=192.168.10.10 \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -v $(pwd)/conf/galera-node0.cnf:/etc/mysql/conf.d/galera.cnf \
    -v $(pwd)/data/node0/mysql:/var/lib/mysql \
    mariadb:10.2 --wsrep-new-cluster

สังเกตว่า ผมใช้ --rm -it เดี๋ยวจะมาเล่าให้ฟังทีหลัง

ที่ node0 รันขึ้นมาด้วย Image mariadb:10.2 และมี parameter --wsrep-new-cluster คือการบอกว่า เครื่องนี้ จะเป็น Primary

หลังจากรันคำสั่งข้างบนไป จะได้

Initializing database
... # ข้าม

Database initialized
MySQL init process in progress...
... # ข้าม
... view(view_id(PRIM,c4673b0e,1) memb {
        c4673b0e,0
} joined {
} left {
} partitioned {
})
... # ข้าม
 WSREP: Quorum results:
        version    = 4,
        component  = PRIMARY,
        conf_id    = 0,
        members    = 1/1 (joined/total),
        act_id     = 0,
        last_appl. = -1,
        protocols  = 0/8/3 (gcs/repl/appl),
        group UUID = c468f36c-93da-11e8-9cba-678b3ddd89a6
... # ข้าม
[Note] mysqld: ready for connections.
...

รันเสร็จ เราจะได้ MariaDB Cluster 1 node ที่เป็น Primary ค้างหน้านี้ไว้ก่อน ให้รัน node1 ต่อ ก่อนรัน node1 อย่าลืมแก้ Galera config ให้เป็นของ node1

docker run -d --name node1 \
    --restart=always \
    --net=db-net \
    --ip=192.168.10.11 \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -v $(pwd)/conf/galera-node1.cnf:/etc/mysql/conf.d/galera.cnf \
    -v $(pwd)/data/node1/mysql:/var/lib/mysql \
    mariadb:10.2

ที่ Logs ของ node0 จะมี

... # ข้าม
...view(view_id(PRIM,79991dba,2) memb {
        79991dba,0
        d2d34f12,0
} joined {
} left {
} partitioned {
})
... # ข้าม
2018-07-30  9:32:28 139968731133696 [Note] WSREP: Quorum results:
        version    = 4,
        component  = PRIMARY,
        conf_id    = 1,
        members    = 1/2 (joined/total),
        act_id     = 7176,
        last_appl. = 0,
        protocols  = 0/8/3 (gcs/repl/appl),
        group UUID = c468f36c-93da-11e8-9cba-678b3ddd89a6

เมื่อรัน node1 เสร็จแล้ว เช็คจำนวนโหนดใน Cluster ที่ node0

MariaDB [(none)]> SHOW STATUS LIKE 'wsrep_cluster_size';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 2     |
+--------------------+-------+
1 row in set (0.00 sec)

ต่อไปก็รันโหนด node2

docker run -d --name node2 \
    --restart=always \
    --net=db-net \
    --ip=192.168.10.12 \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -v $(pwd)/conf/galera-node2.cnf:/etc/mysql/conf.d/galera.cnf \
    -v $(pwd)/data/node2/mysql:/var/lib/mysql \
    mariadb:10.2

หลังจากรันเสร็จแล้ว เช็คจำนวนโหนดใน Cluster อีกที

MariaDB [(none)]> SHOW STATUS LIKE 'wsrep_cluster_size';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 3     |
+--------------------+-------+
1 row in set (0.00 sec)

ตอนนี้ เราได้ Cluster ที่มี 3 node โดยเราสามารถเขียนลงที่โหนดไหนก็ได้

# สร้าง Database ชื่อ node0 ที่ node0
mysql -u root -p -h node0 -e "CREATE DATABASE node0;"
# แสดงรายชื่อ Database ที่ node2
mysql -u root -p -h node2 -e "SHOW DATABASES;"

# สร้าง และ แสดงชื่อ Database ที่โหนดต่างๆ
mysql -u root -p -h node1 -e "CREATE DATABASE node2;"
mysql -u root -p -h node1 -e "SHOW DATABASES;"
mysql -u root -p -h node2 -e "CREATE DATABASE node1;"
mysql -u root -p -h node0 -e "SHOW DATABASES;"

ต่อมา ลองลบ node0 container และข้อมูลที่ node0 มีอยู่ ออกทั้งหมด

docker rm -f node0
rm -rf data/node0

ถ้าดู Logs ที่โหนดอื่นๆ จะเห็น

WSREP: Quorum results:
	version    = 4,
	component  = PRIMARY,
	conf_id    = 5,
	members    = 2/2 (joined/total),
	act_id     = 7179,
	last_appl. = 0,
	protocols  = 0/8/3 (gcs/repl/appl),
	group UUID = c468f36c-93da-11e8-9cba-678b3ddd89a6

และก็ลบ node1 ทิ้งด้วย

docker rm -f node1
rm -rf data/node1

ตอนนี้ จะเหลือแต่ node2 เท่านั้น ที่ยังอยู่

MariaDB [(none)]> SHOW STATUS LIKE 'wsrep_cluster_size';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 1     |
+--------------------+-------+
1 row in set (0.00 sec)

Database ทั้งหมด

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| node0              |
| node1              |
| node2              |
| performance_schema |
+--------------------+
6 rows in set (0.01 sec)

ต่อมา รัน node0 กลับคืนมา

docker run -d --name node0 \
    --restart=always \
    --net=db-net \
    --ip=192.168.10.10 \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -v $(pwd)/conf/galera-node0.cnf:/etc/mysql/conf.d/galera.cnf \
    -v $(pwd)/data/node0/mysql:/var/lib/mysql \
    mariadb:10.2

สังเกตว่า ไม่ได้ใช้ --rm -it แล้ว เดี๋ยวมาเล่า

และก็รัน node1

docker run -d --name node1 \
    --restart=always \
    --net=db-net \
    --ip=192.168.10.11 \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -v $(pwd)/conf/galera-node1.cnf:/etc/mysql/conf.d/galera.cnf \
    -v $(pwd)/data/node1/mysql:/var/lib/mysql \
    mariadb:10.2

ลองดู Database ที่มีอยู่ จะเท่ากับเท่าเดิม


MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| node0              |
| node1              |
| node2              |
| performance_schema |
+--------------------+
6 rows in set (0.02 sec)

หลังจากที่ node0, node1 หายไป แต่ node2 ยังอยู่ ข้อมูลก็ยังอยู่ อันนี้ เรายังมีข้อมูลอยู่ที่ โหนดอื่นๆ ไม่ได้ดับ หรือ โหนดหายไปพร้อมกัน ทั้งหมด แล้ว ถ้ามันหายไปพร้อมกันทั้งหมด เช่น เซิฟเวอร์ถูก reboot จะเป็นยังไงน่ะ ไหน ลองซิ...

ผมจะลอง restart docker เพื่อจำลองการ reboot เซิฟเวอร์

ลุย...

Screen-Shot-2561-07-30-at-17.04.49

Restart docker เสร็จแล้ว โหนดก็กลับมา แล้วข้อมูลยังอยู่ไหมน่ะ

Screen-Shot-2561-07-30-at-17.07.17

ฉิบ หาย แล้ว

หลังจากที่ เซิฟเวอร์ถูก reboot แล้ว login ไม่ได้ ซวยแล้วๆๆๆๆๆ พอลองดูที่ /var/lib/mysql ยังมีข้อมูลอยู่

Screen-Shot-2561-07-30-at-17.09.43

ในรูปคือ ที่ node0 พอลองกับโหนดอื่นๆ ก็ได้ error เหมือนกัน login ไม่ได้

เช็ค Logs ที่ node0 ก็จะเห็นประมาณนี้

 [Warning] WSREP: Quorum: No node with complete state:

	Version      : 4
	Flags        : 0x5
	Protocols    : 0 / 8 / 3
	State        : NON-PRIMARY
	Desync count : 0
	Prim state   : NON-PRIMARY
	Prim UUID    : 00000000-0000-0000-0000-000000000000
	Prim  seqno  : -1
	First seqno  : -1
	Last  seqno  : -1
	Prim JOINED  : 0
	State UUID   : c4aaf165-93df-11e8-bfcf-d6db3193273e
	Group UUID   : 00000000-0000-0000-0000-000000000000
	Name         : 'node1'
	Incoming addr: '192.168.10.11:3306'

	Version      : 4
	Flags        : 0x4
	Protocols    : 0 / 8 / 3
	State        : NON-PRIMARY
	Desync count : 0
	Prim state   : NON-PRIMARY
	Prim UUID    : 00000000-0000-0000-0000-000000000000
	Prim  seqno  : -1
	First seqno  : -1
	Last  seqno  : -1
	Prim JOINED  : 0
	State UUID   : c4aaf165-93df-11e8-bfcf-d6db3193273e
	Group UUID   : 00000000-0000-0000-0000-000000000000
	Name         : 'node0'
	Incoming addr: '192.168.10.10:3306'

	Version      : 4
	Flags        : 0x4
	Protocols    : 0 / 8 / 3
	State        : NON-PRIMARY
	Desync count : 0
	Prim state   : NON-PRIMARY
	Prim UUID    : 00000000-0000-0000-0000-000000000000
	Prim  seqno  : -1
	First seqno  : -1
	Last  seqno  : -1
	Prim JOINED  : 0
	State UUID   : c4aaf165-93df-11e8-bfcf-d6db3193273e
	Group UUID   : 00000000-0000-0000-0000-000000000000
	Name         : 'node2'
	Incoming addr: '192.168.10.12:3306'

[Warning] WSREP: No re-merged primary component found.
[Note] WSREP: Bootstrapped primary 00000000-0000-0000-0000-000000000000 found: 3.
[Note] WSREP: Quorum results:
	version    = 4,
	component  = PRIMARY,
	conf_id    = -1,
	members    = 3/3 (joined/total),
	act_id     = -1,
	last_appl. = -1,
	protocols  = 0/8/3 (gcs/repl/appl),
	group UUID = 00000000-0000-0000-0000-000000000000

ก็มี 3 members แล้วนี่หว่า แต่มันไม่เจอ Primary เราต้อง หาทางเอา Primary กลับมา ผมลองรัน --wsrep-new-cluster ที่ node0 ใหม่ โดยลบ node0 ออกก่อน

docker rm -f node0
docker run --rm -it --name node0 \
    --net=db-net \
    --ip=192.168.10.10 \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -v $(pwd)/conf/galera-node0.cnf:/etc/mysql/conf.d/galera.cnf \
    -v $(pwd)/data/node0/mysql:/var/lib/mysql \
    mariadb:10.2 --wsrep-new-cluster

ปรากฏว่า Container ตาย แต่ถ้าดู Logs ดีๆ จะเห็นว่า

[Note] WSREP: Start replication
[Note] WSREP: 'wsrep-new-cluster' option used, bootstrapping the cluster
[Note] WSREP: Setting initial position to 00000000-0000-0000-0000-000000000000:-1
[ERROR] WSREP: It may not be safe to bootstrap the cluster from this node. It was not the last one to leave the cluster and may not contain all the updates. To force cluster bootstrap with this node, edit the grastate.dat file manually and set safe_to_bootstrap to 1 .

อันนี้ edit the grastate.dat file manually and set safe_to_bootstrap to 1

ผมแก้ไฟล์ grastate.dat

จาก

# GALERA saved state
version: 2.1
uuid:    c468f36c-93da-11e8-9cba-678b3ddd89a6
seqno:   -1
safe_to_bootstrap: 0

แก้

safe_to_bootstrap: 0

เป็น

safe_to_bootstrap: 1

แล้วรันใหม่

docker run --rm -it --name node0 \
    --net=db-net \
    --ip=192.168.10.10 \
    -e MYSQL_ROOT_PASSWORD=123456 \
    -v $(pwd)/conf/galera-node0.cnf:/etc/mysql/conf.d/galera.cnf \
    -v $(pwd)/data/node0/mysql:/var/lib/mysql \
    mariadb:10.2 --wsrep-new-cluster

แล้วก็เข้าไปดูใน Container จะเห็น Cluster size เป็น 1 อยู่ แต่ Database ยังอยู่ครบ

Screen-Shot-2561-07-30-at-17.21.19

จากนั้นก็ restart node1, node2

docker restart node1 node2

จากนั้นก็เป็นการเสร็จพิธี

Screen-Shot-2561-07-30-at-17.24.23


อ่านเพิ่มเติม Introducing the “Safe-To-Bootstrap” feature in Galera Cluster

จบแล้ว เท่านี้ ถ้าใครอ่านแล้วถูกใจ อยากบริจาคค่าเบียร์ ค่ากาแฟ กดบริจาคผ่าน PayPal ได้ที่ GHOST_URL/donate/ กดหลายๆแก้วก็ได้


Source code : https://github.com/narate/mariadb-galera-cluster