쿠버네티스 데이터베이스 오퍼레이터 스터티 2주차 내용

쿠버네티스 오퍼레이터

쿠버네티스 추상화를 통해 관리 대상 소프트웨어의 전체 라이프사이클을 자동화, 애플리케이션을 패키징-배포-관리하는 방법론이고,
오퍼레이터라고하면 오퍼레이터 패턴이라는 개념도 많이 등장하는데
오퍼레이터패턴이란 커스텀 컨트롤러가 사용자가 생성한 Custom Resource를 watch 하고, Custom Resource에 정의된 desired state와 현재 상태를 일치시키기 위한 Custom Resource에 특화된 동작을 하는 것

오퍼레이터 사용방식

오퍼레이터는 https://operatorhub.io/?category=Database 에서 찾아볼 수 있다.

아래의 그림에서 총 5단계가 있으며, 3단계 이상 사용하는 것이 좋다.

선언적 생성방식

아래에는 생성시 사용되는 개념들이다.

  • CRD Custom Resource Definition : 오퍼레이터로 사용할 상태 관리용 객체들의 Spec 을 정의
  • CR Custom Resource : CRD의 Spec 를 지키는 객체들의 실제 상태 데이터 조합
  • CC Custom Controller : CR의 상태를 기준으로 현재의 상태를 규정한 상태로 처리하기 위한 컨트롤 루프
  • 생성 yaml
    apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition        # 사용자 정의 리소스(CRD) 생성
    metadata:
      # name must match the spec fields below, and be in the form: <plural>.< group>
      name: crontabs.stable.example.com   # <NAMES>.<GROUP> 으로 정의
    spec:
      # group name to use for REST API: /apis/<group>/<version>
      group: stable.example.com           # apiVersion 그룹 이름(<GROUP>) 을 지정
      # list of versions supported by this CustomResourceDefinition
      versions:                           # CRD 버전 정의
        - name: v1
          # Each version can be enabled/disabled by Served flag.
          served: true
          # One and only one version must be marked as the storage version.
          storage: true
          schema:
            openAPIV3Schema:
              type: object
              properties: # status에 해당
                spec:
                  type: object
                  properties:
                    cronSpec:
                      type: string
                    image:
                      type: string
                    replicas:
                      type: integer
      # either Namespaced or Cluster
      scope: Namespaced                  # Cluster 레벨 리소스인지 vs 네임스페이스 레벨 리소스인지 지정
      names:
        # plural name to be used in the URL: /apis/<group>/<version>/<plural>
        plural: crontabs                 # 복수 이름
        # singular name to be used as an alias on the CLI and for display
        singular: crontab                # 단수 이름
        # kind is normally the CamelCased singular type. Your resource manifests use this.
        kind: CronTab                    # Kind 이름
        # shortNames allow shorter string to match your resource on the CLI
        shortNames:                      # 축약 이름
        - ct

데이터베이스 k8s로 운영하기위해서는 쿠버네티스 오퍼레이터가 필요하다. 그 이유는 데이터베이스는 클러스터 상에서 노드간 동기화가 주기적으로 이루어져야하고 노드가 죽을 시 다른 노드가 master를 유지하여 서비스 상 장애가 발생하지 않도록 상태를 유지하는 것이 중요하기 때문


MySQL Operator for k8s

쿠버네티스 클러스터에서 MySQL 데이터베이스 인스턴스나 클러스터를 쉽게 배포하고 관리할 수 있게 해주는 오퍼레이터이며, 쿠버네티스의 커스텀 리소스(Custom Resources)와 컨트롤러 (Controllers)를 사용하여 MySQL의 운영 작업을 자동화하고, 데이터베이스 관리의 복잡성을 줄여 줌. MySQL 8.0.29 버전과 함께 릴리즈 됨

 

구축

MySQL Operator Install with Helm

설치시 버전은 https://github.com/mysql/mysql-operator/tags 에서 참조

10/28일 기준으로 container-registry.oracle.com/mysql/community-server:8.2.0 image를 pull 할 수 없고 8.1.0 image만 pull 할수 있으나 operator 2.1.0 이 현재 삭제되어 helm을 받을 수가 없어 kubectl로 배포

# helm chart download
wget https://github.com/mysql/mysql-operator/archive/refs/tags/8.1.0-2.1.0.tar.gz
tar -zxvf 8.1.0-2.1.0.tar.gz
cd mysql-operator-8.1.0-2.1.0/helm

# 설치
helm install mysql-operator ./mysql-operator --namespace mysql-operator --create-namespace
helm get manifest mysql-operator -n mysql-operator

# 설치 확
kubectl get deploy,pod -n mysql-operator
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mysql-operator   1/1     1            1           25s

NAME                                 READY   STATUS    RESTARTS   AGE
pod/mysql-operator-d6ff8f8f6-86fgv   1/1     Running   0          25s

# CRD 확인
kubectl get crd | egrep 'mysql|zalando'
clusterkopfpeerings.zalando.org              2023-10-28T11:13:14Z
innodbclusters.mysql.oracle.com              2023-10-28T11:13:14Z
kopfpeerings.zalando.org                     2023-10-28T11:13:14Z
mysqlbackups.mysql.oracle.com                2023-10-28T11:13:14Z

## CRD 상세 정보 확인
kubectl describe crd innodbclusters.mysql.oracle.com
...생략

## 삭제
helm uninstall mysql-operator -n mysql-operator && kubectl delete ns mysql-operator

MySQL InnoDB Cluster Install with Helm

설치

# 파라미터 파일 생성
cat <<EOT> mycnf-values.yaml
credentials:
  root:
    password: sakilaserverConfig:
  mycnf: |
    [mysqld]
     max_connections=300
     default_authentication_plugin=mysql_native_passwordtls:
  useSelfSigned: true
EOT

# 차트 설치(기본값) : root 사용자(root), 호스트(%), 서버인스턴스(파드 3개), 라우터인스턴스(파드 1개), serverVersion(8.0.35)
# root 사용자 암호(), tls.useSelfSigned(사용), 네임스페이스 생성 및 적용(mysql-cluster)
helm install mycluster ./mysql-innodbcluster --namespace mysql-cluster -f mycnf-values.yaml --create-namespacehelm get values mycluster -n mysql-clusterhelm get manifest mycluster -n mysql-cluster

watch kubectl get innodbcluster,sts,pod,pvc,svc -n mysql-cluster
NAME                                       STATUS   ONLINE   INSTANCES   ROUTERS   AGE
innodbcluster.mysql.oracle.com/mycluster   ONLINE   1        3           1         73s

NAME                         READY   AGE
statefulset.apps/mycluster   1/3     73s

NAME              READY   STATUS    RESTARTS   AGE
pod/mycluster-0   2/2     Running   0          73s
pod/mycluster-1   2/2     Running   0          73s
pod/mycluster-2   2/2     Running   0          73s

NAME                                        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/datadir-mycluster-0   Bound    pvc-83a3e2b9-9792-4cd7-b825-8ed6771df817   2Gi        RWO            gp3            73s
persistentvolumeclaim/datadir-mycluster-1   Bound    pvc-e98a7172-3d00-47ad-a7ef-a6e0778b4c6b   2Gi        RWO            gp3            73s
persistentvolumeclaim/datadir-mycluster-2   Bound    pvc-7d81080a-d600-40a5-8b47-9ecba0f8b3a4   2Gi        RWO            gp3            73s

NAME                          TYPE        CLUSTER-IP	  EXTERNAL-IP   PORT(S)                                                           AGE
service/mycluster             ClusterIP   10.100.241.39   <none>        3306/TCP,33060/TCP,6446/TCP,6448/TCP,6447/TCP,6449/TCP,8443/TCP   73s
service/mycluster-instances   ClusterIP   None            <none>        3306/TCP,33060/TCP,33061/TCP                                      73s -> headless 서비스, 개벌파드로 직접 접근가능

# 삭제
helm uninstall mycluster -n mysql-cluster && kubectl delete ns mysql-cluster

설치 확인

# 설치 확인
kubectl get innodbcluster,sts,pod,pvc,svc,pdb,all -n mysql-cluster
kubectl df-pv
kubectl resource-capacity

## MySQL InnoDB Cluster 구성요소 확인
kubectl get InnoDBCluster -n mysql-cluster
NAME        STATUS   ONLINE   INSTANCES   ROUTERS   AGE
mycluster   ONLINE   3        3           1         2m25s

## 이벤트 확인
kubectl describe innodbcluster -n mysql-cluster | grep Events: -A30
	Normal  Logging           2m47s  kopf      Handler 'on_innodbcluster_field_router_instances/spec.router.instances' succeeded.
  Normal  Logging           2m47s  kopf      Handler 'on_innodbcluster_field_image_pull_policy/spec.imagePullPolicy' succeeded.
  Normal  Logging           2m47s  kopf      Handler 'on_innodbcluster_field_version/spec.version' succeeded.
  Normal  Logging           2m47s  kopf      Handler 'on_innodbcluster_field_instances/spec.instances' succeeded.
  Normal  Logging           2m47s  kopf      Handler 'on_innodbcluster_create' succeeded.
  Normal  Logging           2m46s  kopf      on_innodbcluster_field_tls_use_self_signed
  Normal  Logging           2m46s  kopf      Handler 'on_innodbcluster_field_tls_use_self_signed/spec.tlsUseSelfSigned' succeeded.
  Normal  Logging           2m46s  kopf      Creation is processed: 6 succeeded; 0 failed.

## MySQL InnoDB Cluster 초기 설정 확인
kubectl get configmap -n mysql-cluster mycluster-initconf -o json | jq -r '.data["my.cnf.in"]'
kubectl get configmap -n mysql-cluster mycluster-initconf -o yaml | yh
kubectl describe configmap -n mysql-cluster 

## 서버인스턴스 확인(스테이트풀셋) : 3개의 노드에 각각 파드 생성 확인, 사이드카 컨테이너 배포
kubectl get sts -n mysql-cluster; echo; kubectl get pod -n mysql-cluster -l app.kubernetes.io/component=database -owide
NAME        READY   AGE
mycluster   3/3     3m12s

NAME          READY   STATUS    RESTARTS   AGE     IP              NODE                                               NOMINATED NODE   READINESS GATES
mycluster-0   2/2     Running   0          3m13s   192.168.2.141   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           2/2
mycluster-1   2/2     Running   0          3m13s   192.168.3.115   ip-192-168-3-96.ap-northeast-2.compute.internal    <none>           2/2
mycluster-2   2/2     Running   0          3m13s   192.168.1.25    ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           2/2

## 프로브 확인(Readiness, Liveness, Startup)
 describe pod -n mysql-cluster mycluster-0 | egrep 'Liveness|Readiness:|Startup'
		Liveness:       exec [/livenessprobe.sh] delay=15s timeout=1s period=15s #success=1 #failure=10
    Readiness:      exec [/readinessprobe.sh] delay=10s timeout=1s period=5s #success=1 #failure=10000
    Startup:        exec [/livenessprobe.sh 8] delay=5s timeout=1s period=3s #success=1 #failure=10000

## 서버인스턴스가 사용하는 PV(PVC) 확인 : AWS EBS 볼륨 확인해보기
kubectl get sc
kubectl df-pv
pvc,pv -n mysql-cluster
NAME                                        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/datadir-mycluster-0   Bound    pvc-83a3e2b9-9792-4cd7-b825-8ed6771df817   2Gi        RWO            gp3            3m54s
persistentvolumeclaim/datadir-mycluster-1   Bound    pvc-e98a7172-3d00-47ad-a7ef-a6e0778b4c6b   2Gi        RWO            gp3            3m54s
persistentvolumeclaim/datadir-mycluster-2   Bound    pvc-7d81080a-d600-40a5-8b47-9ecba0f8b3a4   2Gi        RWO            gp3            3m54s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-7d81080a-d600-40a5-8b47-9ecba0f8b3a4   2Gi        RWO            Delete           Bound    mysql-cluster/datadir-mycluster-2   gp3                     3m50s
persistentvolume/pvc-83a3e2b9-9792-4cd7-b825-8ed6771df817   2Gi        RWO            Delete           Bound    mysql-cluster/datadir-mycluster-0   gp3                     3m50s
persistentvolume/pvc-e98a7172-3d00-47ad-a7ef-a6e0778b4c6b   2Gi        RWO            Delete           Bound    mysql-cluster/datadir-mycluster-1   gp3                     3m50s

## 서버인스턴스 각각 접속을 위한 헤드리스 Headless 서비스 확인
kubectl describe svc -n mysql-cluster mycluster-instances

kubectl get svc,ep -n mysql-cluster mycluster-instances
NAME                          TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                        AGE
service/mycluster-instances   ClusterIP   None         <none>        3306/TCP,33060/TCP,33061/TCP   4m37s

NAME                            ENDPOINTS                                                                AGE
endpoints/mycluster-instances   192.168.1.25:33060,192.168.2.141:33060,192.168.3.115:33060 + 6 more...   4m37s

## 라우터인스턴스(디플로이먼트) 확인  : 1대의 파드 생성 확인
deploy -n mysql-cluster; -n mysql-cluster -l app.kubernetes.io/component=router
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
mycluster-router   1/1     1            1           5m6s
NAME                                READY   STATUS    RESTARTS   AGE
mycluster-router-65469cb756-9nbf2   1/1     Running   0          3m38s

## 라우터인스턴스 접속을 위한 서비스(ClusterIP) 확인
svc,ep -n mysql-cluster mycluster

# max_connections 설정 값 확인 : MySQL 라우터를 통한 MySQL 파드 접속 >> Helm 차트 설치 시 파라미터러 기본값(151 -> 300)을 변경함
MIC=mycluster.mysql-cluster.svc.cluster.local
echo "export MIC=mycluster.mysql-cluster.svc.cluster.local" >> /etc/profile
kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$ --password=sakila --sqlx --execute="SHOW VARIABLES LIKE 'max_connections';"
WARNING: Using a password on the command line interface can be insecure.
Variable_name	Value
max_connections	300

정보확인 및 설정

MySQL 접속

Headless 서비스 주소로 개별 MySQL 서버(파드)로 직접 접속 → 각각의 db 서버로 직접접근

headless 이기에 mycluster-0, mycluster-1, mycluster-2 식으로 pod name이 번호를 붙여가며 순차적으로 네이밍 결정되어 고정된 주소로 접근 가능

# MySQL 라우터 접속을 위한 서비스 정보 확인 : 실습 환경은 Cluster-IP Type
kubectl get svc -n mysql-cluster mycluster
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                           AGE
mycluster   ClusterIP   10.100.241.39   <none>        3306/TCP,33060/TCP,6446/TCP,6448/TCP,6447/TCP,6449/TCP,8443/TCP   6m7s

# MySQL 서버(파드) 접속을 위한 서비스 정보 확인 : Headless 서비스
kubectl get svc -n mysql-cluster mycluster-instances
NAME                  TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                        AGE
mycluster-instances   ClusterIP   None         <none>        3306/TCP,33060/TCP,33061/TCP   6m18s

kubectl get pod -n mysql-cluster -l app.kubernetes.io/component=database -owide
NAME          READY   STATUS    RESTARTS   AGE     IP              NODE                                               NOMINATED NODE   READINESS GATES
mycluster-0   2/2     Running   0          6m29s   192.168.2.141   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           2/2
mycluster-1   2/2     Running   0          6m29s   192.168.3.115   ip-192-168-3-96.ap-northeast-2.compute.internal    <none>           2/2
mycluster-2   2/2     Running   0          6m29s   192.168.1.25    ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           2/2

# netshoot 파드에 zsh 접속해서 DNS 쿼리 수행
kubectl run -it --rm netdebug --image=nicolaka/netshoot --restart=Never -- zsh
-------
# dig 툴로 도메인 질의 : <서비스명>.<네임스페이스>.svc 혹은 <서비스명>.<네임스페이스>.svc.cluster.local
# 아래 도메인 주소로 접근 시 MySQL 라우터를 통해서 MySQL 서버(파드)로 접속됨
dig mycluster.mysql-cluster.svc +search +short
10.100.241.39 -> cluster ip 10.100.241.39 와 일치
dig mycluster.mysql-cluster.svc.cluster.local

# Headless 서비스 주소로 개별 MySQL 서버(파드)로 직접 접속을 위한 DNS 쿼리
dig mycluster-instances.mysql-cluster.svc +search
;; ANSWER SECTION:
mycluster-instances.mysql-cluster.svc.cluster.local. 5 IN A 192.168.1.25
mycluster-instances.mysql-cluster.svc.cluster.local. 5 IN A 192.168.3.115
mycluster-instances.mysql-cluster.svc.cluster.local. 5 IN A 192.168.2.141
;; Query time: 0 msec
;; SERVER: 10.100.0.10#53(10.100.0.10) (UDP)
;; WHEN: Sat Oct 28 11:23:34 UTC 2023
;; MSG SIZE  rcvd: 293

dig mycluster-instances.mysql-cluster.svc.cluster.local +short
192.168.3.115
192.168.1.25
192.168.2.141

# MySQL 서버(파드)마다 고유한 SRV 레코드가 있고, 해당 도메인 주소로 접속 시 MySQL 라우터를 경유하지 않고 지정된 MySQL 서버(파드)로 접속됨
dig mycluster-instances.mysql-cluster.svc.cluster.local SRV
..(생략)...
;; ADDITIONAL SECTION:
mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local. 5 IN A	192.168.3.115
mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local. 5 IN A	192.168.2.141
mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local. 5 IN A	192.168.1.25
# mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local 로 접근 시 192.168.3.115 로 맵핑된다.

# zsh 빠져나오기
exit
-------

# 접속 주소 변수 지정
MIC=mycluster.mysql-cluster.svc.cluster.local
MDB1=mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local
MDB2=mycluster-1MDB3=mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local

# MySQL 라우터를 통한 MySQL 파드 접속
# kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$ --password=sakila
kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$ --password=sakila --sqlx --execute='show databases;'
Database
information_schema
mysql
mysql_innodb_cluster_metadata
performance_schema
sys

kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$ --password=sakila --sqlx --execute="SHOW VARIABLES LIKE 'max_connections';"
Variable_name	Value
max_connections	300

# 개별 MySQL 파드 접속 : 헤드리스 서비스
kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$MDB1 --password=sakila --sqlx --execute='SELECT @@hostname;'mycluster-0

kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$MDB2 --password=sakila --sqlx --execute='SELECT @@hostname;'mycluster-1mysqlsh mysqlx://root@$MDB3 --password=sakila --sqlx --execute='SELECT @@hostname;'mycluster-2

MySQL Shell 8.x 를 통한 연결 → shell로 통합접근, 다양한 언어(sql, python, javascript) client 환경을 사용할 수 있음

kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh
MySQL  JS >
MySQL  JS > \connect root@mycluster.mysql-cluster.svc
Fetching schema names for auto-completion... Press ^C to stop.
Your MySQL connection id is 1968 (X protocol)
Server version: 8.1.0 MySQL Community Server - GPL
No default schema selected; type \use <schema> to set one.
MySQL  mycluster.mysql-cluster.svc:33060+ ssl  JS >

## MySQL InnoDB Cluster 상태 확인 : JavaScript 모드
\status
MySQL Shell version 8.1.0

Connection Id:                1968
Default schema:
Current schema:
Current user:                 root@ip-192-168-2-131.ap-northeast-2.compute.internal
SSL:                          Cipher in use: TLS_AES_256_GCM_SHA384 TLSv1.3
Using delimiter:              ;
Server version:               8.1.0 MySQL Community Server - GPL
Protocol version:             X protocol
Client library:               8.1.0
Connection:                   mycluster.mysql-cluster.svc via TCP/IP
TCP port:                     33060
Server characterset:          utf8mb4
Schema characterset:          utf8mb4
Client characterset:          utf8mb4
Conn. characterset:           utf8mb4
Result characterset:          utf8mb4
Compression:                  Disabled
Uptime:                       14 min 8.0000 sec

## Python 모드로 전환
MySQL  mycluster.mysql-cluster.svc:33060+ ssl  JS > \py
Switching to Python mode...
MySQL  mycluster.mysql-cluster.svc:33060+ ssl  Py >

# 종료
\exit

MySQL 라우터 확인 & 프라이머리 변경

# MySQL 라우터 bash 셸 접속
kubectl exec -it -n mysql-cluster deploy/mycluster-router -- bash
--------------------
# help
mysqlrouter --help
mysqlrouter --version

# 관련 파일 확인
ls -al /tmp/mysqlrouter/
total 16
drwx--S--- 5 mysqlrouter mysqlrouter  118 Oct 28 11:16 .
drwxrwsrwx 3 root        mysqlrouter   69 Oct 28 11:16 ..
drwx--S--- 2 mysqlrouter mysqlrouter  116 Oct 28 11:16 data
drwx--S--- 2 mysqlrouter mysqlrouter   29 Oct 28 11:16 log
-rw------- 1 mysqlrouter mysqlrouter 1870 Oct 28 11:16 mysqlrouter.conf
-rw------- 1 mysqlrouter mysqlrouter   87 Oct 28 11:16 mysqlrouter.key
drwx--S--- 2 mysqlrouter mysqlrouter    6 Oct 28 11:16 run
-rwx------ 1 mysqlrouter mysqlrouter  135 Oct 28 11:16 start.sh
-rwx------ 1 mysqlrouter mysqlrouter  158 Oct 28 11:16 stop.sh

cat /tmp/mysqlrouter/mysqlrouter.conf
[DEFAULT]
...
connect_timeout=5
read_timeout=30
dynamic_state=/tmp/mysqlrouter/data/state.json
...

[metadata_cache:bootstrap]  # 라우터에 접속할 InnoDB 클러스터의 정보를 구성하고 관리
cluster_type=gr
router_id=1
user=mysqlrouter
metadata_cluster=mycluster
ttl=0.5                     # MySQL 라우터가 내부적으로 캐싱하고 있는 클러스터 메타데이터를 갱신하는 주기, 단위(초)
auth_cache_ttl=-1
auth_cache_refresh_interval=2
use_gr_notifications=0      # 해당 옵션 활성화시, 클러스터의 그룹 복제 변경사항을 MySQL 라우터가 알람을 받을 수 있다, 알람 받으면 클러스터 메타데이터를 갱신한다

# 각각 MySQL 기본 프로토콜로 연결되는 '읽기전용포트', 읽기-쓰기포트'와 X프로토콜로 연결되는 읽기전용포트', 읽기-쓰기포트'로 총 4개의 TCP 포트를 사용
# role 이 PRIMART 시 : 기본 round-robin 동작, MySQL 라우터 부트스트랩 설정 시 first-available 설정이 자동 지정, 2가지 중 선택(round-robin,first-available)
# role 이 SECONDARY 시 : 기본 round-robin 동작, MySQL 라우터 부트스트랩 설정 시 round-robin-with-fallback 설정이 자동 지정, 3가지 중 선택(round-robinfirst-available,round-robin-with-fallback)
# role 이 PRIMART_AND_SECONDARY 시 : 기본 round-robin 동작, 2가지 중 선택(round-robinfirst-available)
[routing:bootstrap_rw]
bind_address=0.0.0.0
bind_port=6446
destinations=metadata-cache://mycluster/?role=PRIMARY   # 라우팅 전달 대상이 URL 포맷은 동적이 대상임, role 프라이머리 서버로 연결(읽기-쓰기)
routing_strategy=first-available    # 쿼리 요청 전달 전략(4가지): round-robin, round-robin-with-fallback(세컨더리 서버에 RR, 세컨더리 없으면 프라이어머로 RR)        
protocol=classic                    # 쿼리 요청 전달 전략(이어서): first-available(목록 중 사용 가능 첫번째 서버 연결, 연결안되면 그 다음 서버로 시도)
                                    # 쿼리 요청 전달 전략(이어서): next-available(first-available 와 동일하나, 연결 오류 서버는 연결 불가로 표시하고, 연결 대상에서 제외, 단 정적으로 서버 지정시만 가능)
[routing:bootstrap_ro]
bind_address=0.0.0.0
bind_port=6447
destinations=metadata-cache://mycluster/?role=SECONDARY # role 는 어떤 타입의 MySQL 서버로 연결할지를 설정, 여기서는 세컨터리 타입 서버로 연결(읽기전용)
round-robin-with-fallback
     # 3306 기존 mysql TCP 통신 방법

[routing:bootstrap_x_rw]
bind_address=0.0.0.0
bind_port=6448
destinations=metadata-cache://mycluster/?role=PRIMARY
routing_strategy=first-available
x

[routing:bootstrap_x_ro]
bind_address=0.0.0.0
bind_port=6449
destinations=metadata-cache://mycluster/?role=SECONDARY
routing_strategy=round-robin-with-fallback
protocol=x

[http_server]
port=8443
ssl=1
ssl_cert=/tmp/mysqlrouter/data/router-cert.pem
ssl_key=/tmp/mysqlrouter/data/router-key.pem
...

exit
--------------------

# mysqlrouter 설정 확인
kubectl exec -it -n mysql-cluster deploy/mycluster-router -- mysqlrouter --help
kubectl exec -it -n mysql-cluster deploy/mycluster-router -- 
...(생략)...

# 메타데이터 캐시 정보 확인
kubectl exec -it -n mysql-cluster deploy/mycluster-router -- cat /tmp/mysqlrouter/data/state.json | jq
{
  "metadata-cache": {
    "group-replication-id": "65d7ad70-7583-11ee-9cca-5afbb6fcc8c7",
    "cluster-metadata-servers": [
      "mysql://mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local:3306",
      "mysql://mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local:3306",
      "mysql://mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local:3306"
    ]
  },
  "version": "1.0.0"
}

# 라우터 계정 정보 확인
kubectl get secret -n mysql-cluster  mycluster-router -o jsonpath={.data.routerUsername} | base64 -d;echo
mysqlrouter

kubectl get secret -n mysql-cluster  mycluster-router -o jsonpath={.data.routerPassword} | base64 -d;echo
WbiA2-8=sZ9-Jt~qn-GrSzE-ol=x3

# (옵션) 모니터링
watch -d "kubectl exec -it -n mysql-cluster deploy/mycluster-router -- cat /tmp/mysqlrouter/data/state.json"

샘플 대용량 데이터베이스 주입

30만명 직원의 400만 개의 레코드로 구성, 6개의 테이블, 160메가 링크 Github

터미널은 미리 2개를 띄워놓고 시작

각각의 db에 headless로 접근하여 데이터가 동일하다면 복제가 정상적으로 수행됨을 확인 할 수 있음

# [터미널1] 포트 포워딩
kubectl -n mysql-cluster port-forward service/mycluster mysql
Forwarding from 127.0.0.1:3306 -> 6446
Forwarding from [::1]:3306 -> 6446
-> local에서 3306 으로 접근 시 해당 서비스 포트포워딩

# [터미널2] 아래부터는 터미널2에서 입력
------------------------------
# 포트 포워드 확인
ss -tnlp | grep kubectl
LISTEN 0      128        127.0.0.1:3306       0.0.0.0:*    users:(("kubectl",pid=13357,fd=8))
LISTEN 0      128            [::1]:3306          [::]:*    users:(("kubectl",pid=13357,fd=9))

# 샘플 데이터베이스 git clone
git clone https://github.com/datacharmer/test_db && cd test_db/

# 마스터 노드에 mariadb-client 툴 설치
yum install mariadb -y
mysql -h127.0.0.1 -P3306 -uroot -psakila -e "SELECT @@hostname;"
+-------------+
| @@hostname  |
+-------------+
| mycluster-0 |
+-------------+

# To import the data into your MySQL instance, load the data through the mysql command-line tool: 1분 10초 정도 소요
mysql -h127.0.0.1 -P3306 -uroot -psakila -t < employees.sql

# 확인
mysql -h127.0.0.1 -P3306 -uroot -psakila -e "SHOW DATABASES;"
+-------------------------------+
| Database                      |
+-------------------------------+
| employees                     |
| information_schema            |
| mysql                         |
| mysql_innodb_cluster_metadata |
| performance_schema            |
| sys                           |
+-------------------------------+
mysql -h127.0.0.1 -P3306 -uroot -psakila -e "USE employees;SELECT * FROM employees;"
mysql -h127.0.0.1 -P3306 -uroot -psakila -e "USE employees;SELECT * FROM employees LIMIT 10;"
+--------+------------+------------+-----------+--------+------------+
| emp_no | birth_date | first_name | last_name | gender | hire_date  |
+--------+------------+------------+-----------+--------+------------+
|  10001 | 1953-09-02 | Georgi     | Facello   | M      | 1986-06-26 |
|  10002 | 1964-06-02 | Bezalel    | Simmel    | F      | 1985-11-21 |
|  10003 | 1959-12-03 | Parto      | Bamford   | M      | 1986-08-28 |
|  10004 | 1954-05-01 | Chirstian  | Koblick   | M      | 1986-12-01 |
|  10005 | 1955-01-21 | Kyoichi    | Maliniak  | M      | 1989-09-12 |
|  10006 | 1953-04-20 | Anneke     | Preusig   | F      | 1989-06-02 |
|  10007 | 1957-05-23 | Tzvetan    | Zielinski | F      | 1989-02-10 |
|  10008 | 1958-02-19 | Saniya     | Kalloufi  | M      | 1994-09-15 |
|  10009 | 1952-04-19 | Sumant     | Peac      | F      | 1985-02-18 |
|  10010 | 1963-06-01 | Duangkaew  | Piveteau  | F      | 1989-08-24 |
+--------+------------+------------+-----------+--------+------------+

# 각각 헤드리스 서비스 주소로 각각의 mysql 파드로 접속하여 데이터 조회 확인 : 대용량 데이터 복제가 잘 되었는지 확인해보기!
kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$MDB1 --password=sakila --sqlx --execute="USE employees;SELECT * FROM employees LIMIT 5;"
emp_no	birth_date	first_name	last_name	gender	hire_date
10001	1953-09-02	Georgi	Facello	M	1986-06-26
10002	1964-06-02	Bezalel	Simmel	F	1985-11-21
10003	1959-12-03	Parto	Bamford	M	1986-08-28
10004	1954-05-01	Chirstian	Koblick	M	1986-12-01
10005	1955-01-21	Kyoichi	Maliniak	M	1989-09-12
kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$MDB2 --password=sakila --sqlx --execute="USE employees;SELECT * FROM employees LIMIT 5;"
emp_no	birth_date	first_name	last_name	gender	hire_date
10001	1953-09-02	Georgi	Facello	M	1986-06-26
10002	1964-06-02	Bezalel	Simmel	F	1985-11-21
10003	1959-12-03	Parto	Bamford	M	1986-08-28
10004	1954-05-01	Chirstian	Koblick	M	1986-12-01
10005	1955-01-21	Kyoichi	Maliniak	M	1989-09-12
kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$MDB3 --password=sakila --sqlx --execute="USE employees;SELECT * FROM employees LIMIT 5;"
emp_no	birth_date	first_name	last_name	gender	hire_date
10001	1953-09-02	Georgi	Facello	M	1986-06-26
10002	1964-06-02	Bezalel	Simmel	F	1985-11-21
10003	1959-12-03	Parto	Bamford	M	1986-08-28
10004	1954-05-01	Chirstian	Koblick	M	1986-12-01
10005	1955-01-21	Kyoichi	Maliniak	M	1989-09-12
# 정상적으로 데이터가 복제됨

테스트

복제테스트

# 접속
MIC=mycluster.mysql-cluster.svc.cluster.local
kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$MIC --password=sakila --sqlx

CREATE DATABASE test;
USE test;
CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 TEXT NOT NULL);
INSERT INTO t1 VALUES (1, 'Luis');
SELECT * FROM t1;
SHOW BINLOG EVENTS;
+------------------+-----+----------------+-----------+-------------+----------------------------------+
| Log_name         | Pos | Event_type     | Server_id | End_log_pos | Info                             |
+------------------+-----+----------------+-----------+-------------+----------------------------------+
| mycluster.000001 |   4 | Format_desc    |      1000 |         126 | Server ver: 8.1.0, Binlog ver: 4 |
| mycluster.000001 | 126 | Previous_gtids |      1000 |         157 |                                  |
| mycluster.000001 | 157 | Stop           |      1000 |         180 |                                  |
+------------------+-----+----------------+-----------+-------------+----------------------------------+

# Using Group Replication Group Write Consensus : Inspecting a Group's Write Concurrency
SQL > SELECT group_replication_get_write_concurrency();
+-------------------------------------------+
| group_replication_get_write_concurrency() |
+-------------------------------------------+
|                                        10 |
+-------------------------------------------+

kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$MDB1 --password=sakila --sqlx --execute='USE test; SELECT * FROM t1;'
c1	c2
1	Luis
kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$MDB2 --password=sakila --sqlx --execute='USE test; SELECT * FROM t1;'
c1	c2
1	Luis
kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@$MDB3 --password=sakila --sqlx --execute='USE test; SELECT * FROM t1;'
c1	c2
1	Luis

SQL > SELECT group_replication_get_communication_protocol();
+------------------------------------------------+
| group_replication_get_communication_protocol() |
+------------------------------------------------+
| 8.0.27                                         |
+------------------------------------------------+

성능테스트

다수의 MySQL 클라이언트 파드를 통해 MySQL 라우터 시 부하분산 확인

시나리오

  • 3개의 client 가 mysql router를 통해 접근 시 라우터 정책이 first-available 일때와 round-robin-with-fallback 일 경우 접근하는 db 가 어떤것인지를 확인하고 부하분사 여부를 확인한다.
  • 접속 port 6446 는 first-available 이고, 6447 round-robin-with-fallback 이다.
  • 라운드 정책설정은 MySQL 라우터 확인 & 프라이머리 변경 참조
# mysql 클라이언트 파드 YAML 내용 확인
curl -s https://raw.githubusercontent.com/gasida/DOIK/main/2/myclient-new.yaml -o myclient.yaml
cat myclient.yaml | yh

# myclient 파드 1대 배포 : envsubst 활용
PODNAME=myclient1 envsubst < myclient.yaml | kubectl apply -f -

# myclient 파드 추가로 2대 배포
for ((i=2; i<=3; i++)); do PODNAME=myclient$i envsubst < myclient.yaml | kubectl apply -f - ; done

# myclient 파드들 확인
kubectl get pod -l app=myclient
NAME        READY   STATUS    RESTARTS   AGE
myclient1   1/1     Running   0          82s
myclient2   1/1     Running   0          74s
myclient3   1/1     Running   0          72s

# 파드1에서 mysql 라우터 서비스로 접속 확인 : TCP 3306
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SHOW DATABASES;"
+-------------------------------+
| Database                      |
+-------------------------------+
| employees                     |
| information_schema            |
| mysql                         |
| mysql_innodb_cluster_metadata |
| performance_schema            |
| sys                           |
| test                          |
+-------------------------------+
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT @@HOSTNAME,@@SERVER_ID;"
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT @@HOSTNAME,host from information_schema.processlist WHERE ID=connection_id();"

# 파드1에서 mysql 라우터 서비스로 접속 확인 : TCP 6446
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6446 -e "SELECT @@HOSTNAME,@@SERVER_ID;"
+-------------+-------------+
| @@HOSTNAME  | @@SERVER_ID |
+-------------+-------------+
| mycluster-0 |        1000 |
+-------------+-------------+
# 접속하는 host가 0으로 계속 접근(0번 db가 계속 살아 있기때문)

# 파드1에서 mysql 라우터 서비스로 접속 확인 : TCP 6447 >> 3초 간격으로 확인!
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e "SELECT @@HOSTNAME,@@SERVER_ID;"
+-------------+-------------+
| @@HOSTNAME  | @@SERVER_ID |
+-------------+-------------+
| mycluster-2 |        1002 |
+-------------+-------------+
3초 간격
+-------------+-------------+
| @@HOSTNAME  | @@SERVER_ID |
+-------------+-------------+
| mycluster-1 |        1001 |
+-------------+-------------+
# 접속하는 host가 변경됨

kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e "SELECT @@HOSTNAME,@@SERVER_ID;"

# 파드들에서 mysql 라우터 서비스로 접속 확인 : MySQL 라우터정책이 first-available 라서 무조건 멤버 (프라이머리) 첫번쨰로 전달, host 에는 라우터의 IP가 찍힌다.
for ((i=1; i<=3; i++)); do kubectl exec -it myclient$i -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "select @@hostname, @@read_only, @@super_read_only";echo; done
for ((i=1; i<=3; i++)); do kubectl exec -it myclient$i -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT @@HOSTNAME,host from information_schema.processlist WHERE ID=connection_id();";echo; done
for ((i=1; i<=3; i++)); do kubectl exec -it myclient$i -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT @@HOSTNAME;USE employees;SELECT * FROM employees LIMIT $i;";echo; done
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+-------------+-------------------+
| @@hostname  | @@read_only | @@super_read_only |
+-------------+-------------+-------------------+
| mycluster-0 |           0 |                 0 |
+-------------+-------------+-------------------+

mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+-------------+-------------------+
| @@hostname  | @@read_only | @@super_read_only |
+-------------+-------------+-------------------+
| mycluster-0 |           0 |                 0 |
+-------------+-------------+-------------------+

mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+-------------+-------------------+
| @@hostname  | @@read_only | @@super_read_only |
+-------------+-------------+-------------------+
| mycluster-0 |           0 |                 0 |
+-------------+-------------+-------------------+

# 파드들에서 mysql 라우터 서비스로 접속 확인 : TCP 6447 접속 시 round-robin-with-fallback 정책에 의해서 2대에 라운드 로빈(부하분산) 접속됨
for ((i=1; i<=3; i++)); do kubectl exec -it myclient$i -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e "SELECT @@HOSTNAME,host from information_schema.processlist WHERE ID=connection_id();";echo; done
for ((i=1; i<=3; i++)); do kubectl exec -it myclient$i -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e "SELECT @@HOSTNAME;USE employees;SELECT * FROM employees LIMIT $i;";echo; done
for ((i=1; i<=3; i++)); do kubectl exec -it myclient$i -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e "select @@hostname, @@read_only, @@super_read_only";echo; done
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+--------------------------------------------------------+
| @@HOSTNAME  | host                                                   |
+-------------+--------------------------------------------------------+
| mycluster-2 | ip-192-168-2-131.ap-northeast-2.compute.internal:54362 |
+-------------+--------------------------------------------------------+

mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+---------------------------------------------------------------+
| @@HOSTNAME  | host                                                          |
+-------------+---------------------------------------------------------------+
| mycluster-1 | 192-168-2-131.mycluster.mysql-cluster.svc.cluster.local:45424 |
+-------------+---------------------------------------------------------------+

mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+--------------------------------------------------------+
| @@HOSTNAME  | host                                                   |
+-------------+--------------------------------------------------------+
| mycluster-2 | ip-192-168-2-131.ap-northeast-2.compute.internal:54366 |
+-------------+--------------------------------------------------------+

반복적으로 데이터 INSERT 및 MySQL 서버에 복제 확인 : 세컨더리파드에 INSERT 시도

한쪽에서는 insert를 하고 한쪽에서는 조회를 하기 위해 터미너를 두개 생성해서 실습한다.

# 파드1에서 mysql 라우터 서비스로 접속 확인
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila
--------------------
# 간단한 데이터베이스 생성
CREATE DATABASE test;
USE test;
CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 TEXT NOT NULL);
INSERT INTO t1 VALUES (1, 'Luis');
SELECT * FROM t1;
exit
--------------------

# 조회
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "USE test;SELECT * FROM t1;"
+----+------+
| c1 | c2   |
+----+------+
|  1 | Luis |
+----+------+

# 추가 후 조회
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "USE test;INSERT INTO t1 VALUES (2, 'Luis2');"
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "USE test;SELECT * FROM t1;"
+----+-------+
| c1 | c2    |
+----+-------+
|  1 | Luis  |
|  2 | Luis2 |
+----+-------+

# 반복 추가 및 조회
# [터미널1]
for ((i=3; i<=100; i++)); do kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT @@HOSTNAME;USE test;INSERT INTO t1 VALUES ($i, 'Luis$i');";echo; done

# [터미널2]
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "USE test;SELECT * FROM t1;"
+----+--------+
| c1 | c2     |
+----+--------+
|  1 | Luis   |
|  2 | Luis2  |
|  3 | Luis3  |
|  4 | Luis4  |
|  5 | Luis5  |
|  6 | Luis6  |
|  7 | Luis7  |
|  8 | Luis8  |
|  9 | Luis9  |
| 10 | Luis10 |
+----+--------+

# 모니터링 : 신규 터미널 3개
watch -d "kubectl exec -it myclient1 -- mysql -h mycluster-0.mycluster-instances.mysql-cluster.svc -uroot -psakila -e 'USE test;SELECT * FROM t1 ORDER BY c1 DESC LIMIT 5;'"
+----+--------+
| c1 | c2     |
+----+--------+
| 49 | Luis49 |
| 48 | Luis48 |
| 47 | Luis47 |
| 46 | Luis46 |
| 45 | Luis45 |
+----+--------+
watch -d "kubectl exec -it myclient2 -- mysql -h mycluster-1.mycluster-instances.mysql-cluster.svc -uroot -psakila -e 'USE test;SELECT * FROM t1 ORDER BY c1 DESC LIMIT 5;'"
+----+--------+
| c1 | c2     |
+----+--------+
| 49 | Luis49 |
| 48 | Luis48 |
| 47 | Luis47 |
| 46 | Luis46 |
| 45 | Luis45 |
+----+--------+
watch -d "kubectl exec -it myclient3 -- mysql -h mycluster-2.mycluster-instances.mysql-cluster.svc -uroot -psakila -e 'USE test;SELECT * FROM t1 ORDER BY c1 DESC LIMIT 5;'"
+----+--------+]
| c1 | c2     |
+----+--------+
| 49 | Luis49 |
| 48 | Luis48 |
| 47 | Luis47 |
| 46 | Luis46 |
| 45 | Luis45 |
+----+--------+

# (참고) 세컨더리 MySQL 서버 파드에 INSERT 가 되지 않는다 : --super-read-only option
kubectl exec -it myclient1 -- mysql -h mycluster-1.mycluster-instances.mysql-cluster.svc -uroot -psakila -e "USE test;INSERT INTO t1 VALUES (1089, 'Luis1089');" 
혹은
kubectl exec -it myclient1 -- mysql -h mycluster-2.mycluster-instances.mysql-cluster.svc -uroot -psakila -e "USE test;INSERT INTO t1 VALUES (1089, 'Luis1089');" 
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1062 (23000) at line 1: Duplicate entry '1089' for key 't1.PRIMARY'
command terminated with exit code 1

사전 준비: 워드프레스 설치

# NFS 마운트 확인
ssh ec2-user@$N1 sudo df -hT --type nfs4
df: no file systems processed
ssh ec2-user@$N2 sudo df -hT --type nfs4
df: no file systems processed
ssh ec2-user@$N3 sudo df -hT --type nfs4
df: no file systems processed
# 위와 같이 마운트 안되어있는것이 정상 워드프레스 설치 후 생성됨

# MySQL 에 wordpress 데이터베이스 생성
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "create database wordpress;"
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "show databases;"

# 파라미터 파일 생성
cat <<EOT > wp-values.yaml
wordpressUsername: admin
wordpressPassword: "password"
wordpressBlogName: "DOIK Study"
replicaCount: 3
service:
  type: NodePort
ingress:
  enabled: true
  ingressClassName: alb
  hostname: wp.$MyDomain
  path: /*
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
    alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
    alb.ingress.kubernetes.io/success-codes: 200-399
    alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
    alb.ingress.kubernetes.io/group.name: study
    alb.ingress.kubernetes.io/ssl-redirect: '443'
persistence:
  enabled: true
  storageClass: "efs-sc"
  accessModes:
    - ReadWriteMany
mariadb:
  enabled: false
externalDatabase:
  host: mycluster.mysql-cluster.svc
  port: 3306
  user: root
  password: sakila
  database: wordpress
EOT

# wordpress 설치 : MySQL 접속 주소(mycluster.mysql-cluster.svc), MySQL 데이터베이스 이름 지정(wordpress) , 장애 테스트를 위해서 3대의 파드 배포
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-wordpress bitnami/wordpress --version 18.0.7 -f wp-values.yaml
helm get values my-wordpress

# 설치 확인
watch -d kubectl get pod,svc,pvc
kubectl get deploy,ingress,pvc my-wordpress
kubectl get pod -l app.kubernetes.io/instance=my-wordpress
NAME                            READY   STATUS    RESTARTS      AGE
my-wordpress-5c65fbdfb6-bp75s   0/1     Running   0             43s
my-wordpress-5c65fbdfb6-v77f2   0/1     Running   1 (17s ago)   43s
my-wordpress-5c65fbdfb6-vg8g5   0/1     Running   0             43s

kubectl get sc,pv
NAME                                        PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/efs-sc          efs.csi.aws.com         Delete          Immediate              false                  3h26m
storageclass.storage.k8s.io/gp2             kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  3h45m
storageclass.storage.k8s.io/gp3 (default)   ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   3h26m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-54146fe0-658b-4ba0-8225-ff5a5b0915eb   10Gi       RWX            Delete           Bound    default/my-wordpress                efs-sc                  58s
persistentvolume/pvc-7d81080a-d600-40a5-8b47-9ecba0f8b3a4   2Gi        RWO            Delete           Bound    mysql-cluster/datadir-mycluster-2   gp3                     75m
persistentvolume/pvc-83a3e2b9-9792-4cd7-b825-8ed6771df817   2Gi        RWO            Delete           Bound    mysql-cluster/datadir-mycluster-0   gp3                     75m
persistentvolume/pvc-e98a7172-3d00-47ad-a7ef-a6e0778b4c6b   2Gi        RWO            Delete           Bound    mysql-cluster/datadir-mycluster-1   gp3                     75m

# NFS 마운트 확인
ssh ec2-user@$N1 sudo df -hT --type nfs4
Filesystem           Type  Size  Used Avail Use% Mounted on
127.0.0.1:/          nfs4  8.0E     0  8.0E   0% /var/lib/kubelet/pods/c41481ea-8599-4aa8-ac12-de9b61882956/volumes/kubernetes.io~csi/pvc-54146fe0-658b-4ba0-8225-ff5a5b0915eb/mount
127.0.0.1:/wordpress nfs4  8.0E     0  8.0E   0% /var/lib/kubelet/pods/c41481ea-8599-4aa8-ac12-de9b61882956/volume-subpaths/pvc-54146fe0-658b-4ba0-8225-ff5a5b0915eb/wordpress/0
ssh ec2-user@$N2 sudo df -hT --type nfs4
ssh ec2-user@$N3 sudo df -hT --type nfs4
Filesystem           Type  Size  Used Avail Use% Mounted on
127.0.0.1:/          nfs4  8.0E     0  8.0E   0% /var/lib/kubelet/pods/0991bc9d-4e06-49a3-aa56-1f102ca04220/volumes/kubernetes.io~csi/pvc-54146fe0-658b-4ba0-8225-ff5a5b0915eb/mount
127.0.0.1:/wordpress nfs4  8.0E     0  8.0E   0% /var/lib/kubelet/pods/0991bc9d-4e06-49a3-aa56-1f102ca04220/volume-subpaths/pvc-54146fe0-658b-4ba0-8225-ff5a5b0915eb/wordpress/0

# Wordpress 웹 접속 주소 확인 : 블로그, 관리자
echo -e "Wordpress Web   URL = https://wp.$MyDomain"
echo -e "Wordpress Admin URL = https://wp.$MyDomain/admin"   # 관리자 페이지 : admin, password

# 모니터링
while true; do kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT post_title FROM wordpress.wp_posts;"; date;sleep 1; done
+----------------+
| post_title     |
+----------------+
| Hello world!   |
| Sample Page    |
| Privacy Policy |
+----------------+

# (참고) EFS 확인
mount -t efs -o tls $EFS_ID:/ /mnt/myefs
df -hT --type nfs4
tree /mnt/myefs/ -L 4

# (참고) 관리자 로그인 후 새 글 작성(이미지 첨부) 후 아래 확인
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT * FROM wordpress.wp_term_taxonomy;"
+------------------+---------+----------+-------------+--------+-------+
| term_taxonomy_id | term_id | taxonomy | description | parent | count |
+------------------+---------+----------+-------------+--------+-------+
|                1 |       1 | category |             |      0 |     2 |
|                2 |       2 | wp_theme |             |      0 |     1 |
+------------------+---------+----------+-------------+--------+-------+
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT post_content FROM wordpress.wp_posts;"

장애 테스트

[장애1] MySQL 서버 파드(인스턴스) 1대 강제 삭제 및 동작 확인 : 워드프레스 정상 접속 및 포스팅 작성 가능, 데이터베이스에 반복해서 INSERT

사전 준비

mycluster-1 이 PRIMARY 인 상황

# PRIMARY 파드 정보 확인
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e 'SELECT VIEW_ID FROM performance_schema.replication_group_member_stats LIMIT 1;SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members;'
+---------------------+
| VIEW_ID             |
+---------------------+
| 16984917709340217:5 |
+---------------------+
+-----------------------------------------------------------------+-------------+
| MEMBER_HOST                                                     | MEMBER_ROLE |
+-----------------------------------------------------------------+-------------+
| mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local | PRIMARY     |
| mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
| mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
+-----------------------------------------------------------------+-------------+

kubectl get pod -n mysql-cluster -owide
mycluster-0                         2/2     Running   0          127m   192.168.2.141   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           2/2
mycluster-1                         2/2     Running   0          127m   192.168.3.115   ip-192-168-3-96.ap-northeast-2.compute.internal    <none>           2/2
mycluster-2                         2/2     Running   0          127m   192.168.1.25    ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           2/2
mycluster-router-65469cb756-9nbf2   1/1     Running   0          126m   192.168.2.131   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           <none>

# 파드들에서 mysql 라우터 서비스로 접속 확인 : TCP 6447 접속 시 round-robin-with-fallback 정책에 의해서 2대에 라운드 로빈(부하분산) 접속됨 >> 3초 간격으로 확인!
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e "SELECT @@HOSTNAME,@@SERVER_ID;"
+-------------+-------------+
| @@HOSTNAME  | @@SERVER_ID |
+-------------+-------------+
| mycluster-2 |        1002 |
+-------------+-------------+
3초 간격
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e "SELECT @@HOSTNAME,@@SERVER_ID;"
+-------------+-------------+
| @@HOSTNAME  | @@SERVER_ID |
+-------------+-------------+
| mycluster-1 |        1001 |
+-------------+-------------+

장애 동작 확인

mycluster-1 이 PRIMARY 이므로 mycluster-1 을 삭제 후 wordpress에서 게시글 작성 가능확인

# 모니터링 : 터미널 3개
watch -d 'kubectl get pod -o wide -n mysql-cluster;echo;kubectl get pod -o wide'
while true; do kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e 'SELECT VIEW_ID FROM performance_schema.replication_group_member_stats LIMIT 1;SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members;'; date;sleep 1; done
while true; do kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e 'SELECT @@HOSTNAME;'; date;sleep 2; done

# 신규터미널4 : test 데이터베이스에 원하는 갯수 만큼 데이터 INSERT, CTRL+C 로 취소
for ((i=1001; i<=5000; i++)); do kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT NOW();INSERT INTO test.t1 VALUES ($i, 'Luis$i');";echo; done

# 신규터미널5 : 프라이머리 파드 삭제 kubectl delete pod -n mysql-cluster <현재 프라이머리 MySQL 서버파드 이름> && kubectl get pod -n mysql-cluster -w
kubectl delete pod -n mysql-cluster mycluster-1 && kubectl get pod -n mysql-cluster -w
NAME                                READY   STATUS        RESTARTS   AGE    IP    	    NODE                                               NOMINATED NODE   READINESS GATES
mycluster-0                         2/2     Running       0          2m8s   192.168.2.141   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           2/2
mycluster-1                         2/2     Terminating   0          134m   192.168.3.115   ip-192-168-3-96.ap-northeast-2.compute.internal    <none>           2/2
mycluster-2                         2/2     Running       0          134m   192.168.1.25    ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           2/2
mycluster-router-65469cb756-9nbf2   1/1     Running       0          133m   192.168.2.131   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           <none>

NAME                            READY   STATUS    RESTARTS        AGE   IP              NODE                                               NOMINATED NODE   READINESS GATES
my-wordpress-5c65fbdfb6-4b8z7   1/1     Running   1 (9m11s ago)   12m   192.168.3.61    ip-192-168-3-96.ap-northeast-2.compute.internal    <none>           <none>
my-wordpress-5c65fbdfb6-mxbx7   1/1     Running   1 (11m ago)     12m   192.168.2.179   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           <none>
my-wordpress-5c65fbdfb6-nfk99   1/1     Running   1 (9m58s ago)   12m   192.168.1.218   ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           <none>
myclient1                       1/1     Running   0               88m   192.168.1.203   ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           <none>
myclient2                       1/1     Running   0               88m   192.168.3.185   ip-192-168-3-96.ap-northeast-2.compute.internal    <none>           <none>
myclient3                       1/1     Running   0               88m   192.168.2.96    ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           <none>

+---------------------+
| VIEW_ID             |
+---------------------+
| 16984917709340217:6 |
+---------------------+
+-----------------------------------------------------------------+-------------+
| MEMBER_HOST                                                     | MEMBER_ROLE |
+-----------------------------------------------------------------+-------------+
| mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local | PRIMARY     |
| mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
+-----------------------------------------------------------------+-------------+

# 워드프레스에 글 작성 및 접속 확인 : 1초 미만으로 자동 절체! >> 원상복구 FailBack 확인(파드 재생성 후 그룹 멤버 Join 확인)
# 만약 <세컨더리 MySQL 서버파드> 를 삭제했을 경우에는 자동 Join 되지 않음 >> 아래 수동 Join 실행하자

mycluster-2가 PRIMARY로 바로 변경되었고 wordpress에서도 정상적으로 posting 가능하며 조회에도 이상없음을 확인함으로 써primary db가 죽어도 바로 복구되며 정상적인 서비스 운용이 가능함을 확인 할 수 있음

[장애2] MySQL 서버 파드(인스턴스) 가 배포된 노드 1대 drain 설정 및 동작 확인 : 워드프레스 정상 접속 및 포스팅 작성 가능, 데이터베이스에 반복해서 INSERT 시도

mycluster-2 가 PRIMARY 인 상황

# 모니터링 : 터미널 3개 >> 장애1 모니터링과 상동
watch -d 'kubectl get pod -o wide -n mysql-cluster;echo;kubectl get pod -o wide'
while true; do kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e 'SELECT VIEW_ID FROM performance_schema.replication_group_member_stats LIMIT 1;SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members;'; date;sleep 1; done
while true; do kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e 'SELECT @@HOSTNAME;'; date;sleep 2; done
NAME                                READY   STATUS    RESTARTS   AGE     IP              NODE                                               NOMINATED NODE   READINESS GATES
mycluster-0                         2/2     Running   0          4m57s   192.168.2.141   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           2/2
mycluster-1                         2/2     Running   0          107s    192.168.3.80    ip-192-168-3-96.ap-northeast-2.compute.internal    <none>           2/2
mycluster-2                         2/2     Running   0          137m    192.168.1.25    ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           2/2
mycluster-router-65469cb756-9nbf2   1/1     Running   0          136m    192.168.2.131   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           <none>

NAME                            READY   STATUS    RESTARTS	AGE   IP              NODE                                               NOMINATED NODE   READINESS GATES
my-wordpress-5c65fbdfb6-4b8z7   1/1     Running   1 (12m ago)   15m   192.168.3.61    ip-192-168-3-96.ap-northeast-2.compute.internal    <none>           <none>
my-wordpress-5c65fbdfb6-mxbx7   1/1     Running   1 (14m ago)   15m   192.168.2.179   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           <none>
my-wordpress-5c65fbdfb6-nfk99   1/1     Running   1 (12m ago)   15m   192.168.1.218   ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           <none>
myclient1                       1/1     Running   0             91m   192.168.1.203   ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           <none>
myclient2                       1/1     Running   0             91m   192.168.3.185   ip-192-168-3-96.ap-northeast-2.compute.internal    <none>           <none>
myclient3                       1/1     Running   0             91m   192.168.2.96    ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           <none>
+---------------------+
| VIEW_ID             |
+---------------------+
| 16984917709340217:7 |
+---------------------+
+-----------------------------------------------------------------+-------------+
| MEMBER_HOST                                                     | MEMBER_ROLE |
+-----------------------------------------------------------------+-------------+
| mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
| mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local | PRIMARY     |
| mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
+-----------------------------------------------------------------+-------------+
+-------------+
| @@HOSTNAME  |
+-------------+
| mycluster-0 |
+-------------+
Sat Oct 28 22:32:52 KST 2023
mysql: [Warning] Using a password on the command line interface can be insecure.
+-------------+
| @@HOSTNAME  |
+-------------+
| mycluster-1 |
+-------------+

# 신규터미널4 : test 데이터베이스에 원하는 갯수 만큼 데이터 INSERT, CTRL+C 로 취소
for ((i=5001; i<=10000; i++)); do kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e "SELECT NOW();INSERT INTO test.t1 VALUES ($i, 'Luis$i');";echo; done

# 신규터미널5 : EC2 노드 1대 drain(중지) 설정 : 세컨더리 노드 먼저 테스트 =>> 이후 프라이머리 노드 테스트 해보자! 결과 비교!
kubectl get pdb -n mysql-cluster # 왜 오퍼레이터는 PDB 를 자동으로 설정했을까요?
# kubectl drain <<노드>> --ignore-daemonsets --delete-emptydir-data
kubectl get node
NODE=<각자 자신의 EC2 노드 이름 지정>
NODE=ip-192-168-3-96.ap-northeast-2.compute.internal # 3번째 node을 drain
kubectl drain $NODE --ignore-daemonsets --delete-emptydir-data --force && kubectl get pod -n mysql-cluster -w

# 워드프레스에 글 작성 및 접속 확인 & INSERT 및 확인

# 노드 상태 확인
kubectl get node
NAME                                               STATUS                     ROLES    AGE     VERSION
ip-192-168-1-32.ap-northeast-2.compute.internal    Ready                      <none>   4h42m   v1.27.5-eks-43840fb
ip-192-168-2-161.ap-northeast-2.compute.internal   Ready                      <none>   4h42m   v1.27.5-eks-43840fb
ip-192-168-3-96.ap-northeast-2.compute.internal    Ready,SchedulingDisabled   <none>   4h42m   v1.27.5-eks-43840fb

# 파드 상태 확인
kubectl get pod -n mysql-cluster -l app.kubernetes.io/component=database -owide
NAME          READY   STATUS    RESTARTS   AGE    IP              NODE                                               NOMINATED NODE   READINESS GATES
mycluster-0   2/2     Running   0          9m4s   192.168.2.141   ip-192-168-2-161.ap-northeast-2.compute.internal   <none>           2/2
mycluster-1   0/2     Pending   0          18s    <none>          <none>                                             <none>           0/2
mycluster-2   2/2     Running   0          141m   192.168.1.25    ip-192-168-1-32.ap-northeast-2.compute.internal    <none>           2/2

# db 확인
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e 'SELECT VIEW_ID FROM performance_schema.replication_group_member_stats LIMIT 1;SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members;'; date;sleep 1; done
+---------------------+
| VIEW_ID             |
+---------------------+
| 16984917709340217:8 |
+---------------------+
+-----------------------------------------------------------------+-------------+
| MEMBER_HOST                                                     | MEMBER_ROLE |
+-----------------------------------------------------------------+-------------+
| mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local | PRIMARY     |
| mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
+-----------------------------------------------------------------+-------------+

# EC2 노드 1대 uncordon(정상복귀) 설정
# kubectl uncordon <<노드>>
kubectl uncordon $NODE

node가 drain 되었을 때 wordpress 접속 시 접속이 바로 안됬지만 금방 다른 노드에 떠있는 파드로 연결되어 정상적으로 접속되었고 글도 바로 작성가능하며 db도 primary가 바로 변경되었음.

scale 테스트

MySQL 서버 파드(인스턴스) / 라우터 파드 증가 및 감소해보기

# 현재 MySQL InnoDB Cluster 정보 확인 : 서버파드(인스턴스)는 3대, 라우터파드(인스턴스)는 1대
kubectl get innodbclusters -n mysql-cluster
NAME        STATUS   ONLINE   INSTANCES   ROUTERS   AGE
mycluster   ONLINE   3        3           1         17m

# 모니터링
while true; do kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila -e 'SELECT VIEW_ID FROM performance_schema.replication_group_member_stats LIMIT 1;SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members;'; date;sleep 1; done

# MySQL 서버 파드(인스턴스) 2대 추가 : 기본값(serverInstances: 3, routerInstances: 1) >> 복제 그룹 멤버 정상 상태(그후 쿼리 분산)까지 다소 시간이 걸릴 수 있다(데이터 복제 등)
helm upgrade mycluster mysql-operator/mysql-innodbcluster --reuse-values --set serverInstances=5 --namespace mysql-cluster

# MySQL 라우터 파드 3대로 증가 
helm upgrade mycluster mysql-operator/mysql-innodbcluster --reuse-values --set routerInstances=3 --namespace mysql-cluster

# 확인
kubectl get innodbclusters -n mysql-cluster
NAME        STATUS   ONLINE   INSTANCES   ROUTERS   AGE
mycluster   ONLINE   3        5           3         145m
kubectl get pod -n mysql-cluster -l app.kubernetes.io/component=database
kubectl get pod -n mysql-cluster -l app.kubernetes.io/component=router

# MySQL 서버 파드(인스턴스) 1대 삭제 : 스테이트풀셋이므로 마지막에 생성된 서버 파드(인스턴스)가 삭제됨 : PV/PVC 는 어떻게 될까요?
helm upgrade mycluster mysql-operator/mysql-innodbcluster --reuse-values --set serverInstances=4 --namespace mysql-cluster

# MySQL 라우터 파드 1대로 축소
helm upgrade mycluster mysql-operator/mysql-innodbcluster --reuse-values --set routerInstances=1 --namespace mysql-cluster

# 확인
kubectl get innodbclusters -n mysql-cluster
NAME        STATUS   ONLINE   INSTANCES   ROUTERS   AGE
mycluster   ONLINE   3        2           1         148m

+ Recent posts