스터디/DOIK

K8S MySQL Operator 설치 - DOIK 스터디 2주차

시스템 엔지니어 2022. 6. 1. 23:54

K8S MySQL Operator 설치

apt install mariadb-client -y
mysql -h $MYSQLIP -uroot -psakila -e "SELECT @@hostname;SELECT @@max_connections;"​
# service.yaml
---
apiVersion: v1
kind: Service
metadata:
  labels:
    application: kube-ops-view
    component: frontend
  name: kube-ops-view
spec:
  selector:
    application: kube-ops-view
    component: frontend
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080

# deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    application: kube-ops-view
    component: frontend
  name: kube-ops-view
spec:
  replicas: 1
  selector:
    matchLabels:
      application: kube-ops-view
      component: frontend
  template:
    metadata:
      labels:
        application: kube-ops-view
        component: frontend
    spec:
      nodeSelector:
        kubernetes.io/hostname: k8s-m
      tolerations:
        - effect: NoSchedule
          key: node-role.kubernetes.io/master
          operator: Exists
      serviceAccountName: kube-ops-view
      containers:
      - name: service
        # see https://github.com/hjacobs/kube-ops-view/releases
        image: hjacobs/kube-ops-view:20.4.0
        args:
        # remove this option to use built-in memory store
        - --redis-url=redis://kube-ops-view-redis:6379
        # example to add external links for nodes and pods
        # - --node-link-url-template=https://kube-web-view.example.org/clusters/{cluster}/nodes/{name}
        # - --pod-link-url-template=https://kube-web-view.example.org/clusters/{cluster}/namespaces/{namespace}/pods/{name}
        ports:
        - containerPort: 8080
          protocol: TCP
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          timeoutSeconds: 1
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 30
          timeoutSeconds: 10
          failureThreshold: 5
        resources:
          limits:
            cpu: 200m
            memory: 100Mi
          requests:
            cpu: 50m
            memory: 50Mi
        securityContext:
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          runAsUser: 1000

설치

helm repo add mysql-operator https://mysql.github.io/mysql-operator/
helm install mysql-operator mysql-operator/mysql-operator --namespace mysql-operator --create-namespace
kubectl describe crd innodbclusters.mysql.oracle.com && kubectl get crd | grep -v calico
helm install mycluster mysql-operator/mysql-innodbcluster --set credentials.root.password='sakila' --set tls.useSelfSigned=true --namespace mysql-cluster --create-namespace
kubectl get configmap -n mysql-cluster mycluster-initconf -o yaml
kubectl describe cm -n mysql-cluster mycluster-initconf

# 접속 주소 변수 지정
MIC=mycluster.mysql-cluster.svc.cluster.local
MDB1=mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local
MDB2=mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local
MDB3=mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local
MYSQLIP=$(kubectl get svc -n mysql-cluster mycluster -o jsonpath={.spec.clusterIP})

접속

apt install mariadb-client -y
mysql -h $MYSQLIP -uroot -psakila -e "SELECT @@hostname;SELECT @@max_connections;"

MySQL 라우터를 통한 MySQL 파드 접속

kubectl exec -it -n mysql-operator deploy/mysql-operator -- mysqlsh mysqlx://root@mycluster.mysql-cluster.svc.cluster.local --password=sakila --sqlx --execute='show databases;'

샘플 데이터베이스 git clone 및 데이터 삽입

# 샘플 데이터베이스 git clone 및 IMPORT : 1분 10초 정도 소요
git clone https://github.com/datacharmer/test_db && cd test_db/
mysql -h $MYSQLIP -uroot -psakila -t < employees.sql && cd
mysql -h $MYSQLIP -uroot -psakila -e "SELECT * FROM employees.employees;"
mysql -h $MYSQLIP -uroot -psakila -e "SELECT * FROM employees.employees LIMIT 10;"

mysql router 설정 확인 및 메타데이터 캐시 정보 확인

# mysqlrouter 설정 확인
kubectl exec -it -n mysql-cluster deploy/mycluster-router -- cat /tmp/mysqlrouter/mysqlrouter.conf
# mysqlrouterd 에 메타데이터 캐시 정보 확인 
kubectl exec -it -n mysql-cluster deploy/mycluster-router -- cat /tmp/mysqlrouter/data/state.json

클라이언트 파드를 통해 부하분산 확인

PODNAME=myclient1 envsubst < ~/DOIK/2/myclient.yaml | kubectl apply -f -

${PODNAME} 을 치환해준다.

추가로 2대 배포 / myclient[1,2,3]

for ((i=2; i<=3; i++)); do PODNAME=myclient$i envsubst < ~/DOIK/2/myclient.yaml | kubectl apply -f - ; done

# 클라이언트 파드
---
apiVersion: v1
kind: Pod
metadata:
  name: ${PODNAME}
  labels:
    app: myclient
spec:
  nodeName: k8s-m
  containers:
  - name: ${PODNAME}
    image: mysql:8.0.29
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0

파드들에서 mysql 라우터 서비스로 접속 확인

  • 부하분산 되지 않음
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

클라이언트 파드 myclient1에서 mysql 라우터 서비스로 접속 확인 : TCP 6446

  • 부하분산 되지 않음
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6446 -e "SELECT @@HOSTNAME,@@SERVER_ID;"

클라이언트 파드 myclient1에서 mysql 라우터 서비스로 접속 확인 : TCP 6447

  • secondary만 부하분산 됨
kubectl exec -it myclient1 -- mysql -h mycluster.mysql-cluster -uroot -psakila --port=6447 -e "SELECT @@HOSTNAME,@@SERVER_ID;"
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

설명

  • Mysql 라우터 정책이 first-available 이라서 무조건 primary 첫번째로 전달
  • 6447 port는 round-robin-with-fallback 정책에 의해서 2대에 라운드 로빈(부하분산) 접속 됨

데이터베이스에 Insert 및 MySQL 서버에 복제 확인

# 모니터링
watch -n 1 -d "kubectl exec -it myclient1 -- mysql -h mycluster-0.mycluster-instances.mysql-cluster.svc -uroot -psakila -e 'SELECT * FROM test.t1 ORDER BY c1 DESC LIMIT 5;'"
watch -n 1 -d "kubectl exec -it myclient2 -- mysql -h mycluster-1.mycluster-instances.mysql-cluster.svc -uroot -psakila -e 'SELECT * FROM test.t1 ORDER BY c1 DESC LIMIT 5;'"
watch -n 1 -d "kubectl exec -it myclient3 -- mysql -h mycluster-2.mycluster-instances.mysql-cluster.svc -uroot -psakila -e 'SELECT * FROM test.t1 ORDER BY c1 DESC LIMIT 5;'"

# 마스터노드 자체에서 test 데이터베이스에 97개의 데이터 INSERT
for ((i=3; i<=100; i++)); do mysql -h $MYSQLIP -uroot -psakila -e "SELECT @@HOSTNAME;INSERT INTO test.t1 VALUES ($i, 'Luis$i');";echo; done
mysql -h $MYSQLIP -uroot -psakila -e "SELECT * FROM test.t1;"

# 마스터노드 자체에서 test 데이터베이스에 원하는 갯수 만큼 데이터 INSERT, CTRL+C 로 취소
for ((i=101; i<=1000; i++)); do mysql -h $MYSQLIP -uroot -psakila -e "INSERT INTO test.t1 VALUES ($i, 'Luis$i');";echo; done

작업 전

작업 후

 

워드프레스 설치

# MySQL 에 wordpress 데이터베이스 생성
mysql -h $MYSQLIP -uroot -psakila -e "create database wordpress;"

# wordpress 설치 : MySQL 접속 주소(mycluster.mysql-cluster.svc), MySQL 데이터베이스 이름 지정(wordpress) , 장애 테스트를 위해서 2대의 파드 배포(2분~4분 정도 소요)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-wordpress \
  --set replicaCount=2 \
  --set wordpressUsername=admin \
  --set wordpressPassword=password \
  --set wordpressBlogName="DOIK Study" \
  --set service.type=NodePort \
  --set mariadb.enabled=false \
  --set persistence.storageClass="nfs-client" \
  --set persistence.accessMode=ReadWriteMany \
  --set externalDatabase.host=mycluster.mysql-cluster.svc \
  --set externalDatabase.user=root \
  --set externalDatabase.password=sakila \
  --set externalDatabase.database=wordpress \
    bitnami/wordpress --version 14.3.1

helm get values my-wordpress

NFS 마운트 확인

duf -hide local,special
sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@k8s-w1 duf -hide local,special
sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@k8s-w2 duf -hide local,special
sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@k8s-w3 duf -hide local,special

Wordpress 웹 접속

[장애1] MySQL 서버 파드(인스턴스) 1대 강제 삭제 및 동작 확인

  • 데이터 4천개를 입력하는 동안 파드 삭제
# 데이터 4000개 입력
# 마스터노드 자체에서 test 데이터베이스에 원하는 갯수 만큼 데이터 INSERT, CTRL+C 로 취소, 대략 1분정도 기간내에 입력 완료됨
for ((i=1001; i<=5000; i++)); do mysql -h $MYSQLIP -uroot -psakila -e "SELECT NOW();INSERT INTO test.t1 VALUES ($i, 'Luis$i');";echo; done

# 파드 삭제 kubectl delete pod -n mysql-cluster <현재 프라이머리 MySQL 서버파드 이름> && kubectl get pod -n mysql-cluster -w
kubectl delete pod -n mysql-cluster mycluster-0 && kubectl get pod -n mysql-cluster -w

Primary pod 삭제

  • 1초 미만으로 단절 발생
  • secondary pod 삭제시 단절 발생하지 않음

# 반복 조회
while true; do mysql -h $MYSQLIP -uroot -psakila -e "SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members;"; date; sleep 1; done
+-----------------------------------------------------------------+-------------+
| MEMBER_HOST                                                     | MEMBER_ROLE |
+-----------------------------------------------------------------+-------------+
| mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
| mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local | PRIMARY     |
| mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
+-----------------------------------------------------------------+-------------+

# MySQL 서버 파드(인스턴스) 1대 강제 삭제
kubectl delete pod -n mysql-cluster mycluster-0

# 반복 조회 : 프라이머리 멤버가 mycluster-1 로 변경됨
+-----------------------------------------------------------------+-------------+
| MEMBER_HOST                                                     | MEMBER_ROLE |
+-----------------------------------------------------------------+-------------+
| mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local | PRIMARY     |
| mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
+-----------------------------------------------------------------+-------------+

# 스테이트풀셋이므로 자동으로 MySQL 서버 파드 생성 시작! 
kubectl get pod -n mysql-cluster -l app.kubernetes.io/component=database
NAME          READY   STATUS    RESTARTS   AGE
mycluster-0   2/2     Running   0          118s
mycluster-1   2/2     Running   0          150m
mycluster-2   2/2     Running   0          150m

+-----------------------------------------------------------------+-------------+
| MEMBER_HOST                                                     | MEMBER_ROLE |
+-----------------------------------------------------------------+-------------+
| mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local | PRIMARY     |
| mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
| mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
+-----------------------------------------------------------------+-------------+

# Group Replication 이 관리하는 멤버 목록과 상태 정보(View ID) 확인 : 값 변경 확인!
mysql -h $MYSQLIP -uroot -psakila -e "SELECT VIEW_ID FROM performance_schema.replication_group_member_stats LIMIT 1;"
+---------------------+
| VIEW_ID             |
+---------------------+
| 16524381853527486:5 |
+---------------------+

[장애2] MySQL 서버 파드(인스턴스) 가 배포된 노드 1대 drain 설정 및 동작 확인

# CoreDNS 미리 늘려놓기
kubectl scale deployment -n kube-system coredns --replicas=4

# 모니터링
watch -d 'kubectl get pod -o wide -n mysql-cluster;echo;kubectl get pod -o wide'
while true; do mysql -h $MYSQLIP -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 mysql -h $MYSQLIP -uroot -psakila --port=6447 -e 'select @@hostname;'; date;sleep 1; done
while true; do mysql -h $MYSQLIP -uroot -psakila --port=6447 -e 'SELECT * FROM test.t1 ORDER BY c1 DESC LIMIT 5'; date;sleep 1; done

# 마스터노드 자체에서 test 데이터베이스에 원하는 갯수 만큼 데이터 INSERT, CTRL+C 로 취소, 대략 1분정도 기간내에 입력 완료됨
for ((i=5001; i<=10000; i++)); do mysql -h $MYSQLIP -uroot -psakila -e "SELECT NOW();USE test;INSERT INTO t1 VALUES ($i, 'Luis$i');";echo; done

# EC2 노드 1대 drain(중지) 설정 : 세컨더리 노드 먼저 테스트 =>> 이후 프라이머리 노드 테스트 해보자! 결과 비교!
kubectl get pdb -n mysql-cluster # 왜 오퍼레이터는 PDB 를 자동으로 설정했을까요?
# kubectl drain <<노드>> --ignore-daemonsets --delete-emptydir-data
kubectl drain k8s-w2 --ignore-daemonsets --delete-emptydir-data
  • 1대가 쫓겨나도 데이터 입력에는 이상이 없다.

추가로 1대 더 drain 테스트

kubectl drain k8s-w3 --ignore-daemonsets --delete-emptydir-data
... (아래 에러 메시지 반복 출력)
err or when evicting pods/"mycluster-1" -n "mysql-cluster" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod mysql-cluster/mycluster-1
error when evicting pods/"mycluster-1" -n "mysql-cluster" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod mysql-cluster/mycluster-1
...

PDB 정책에 의해서 mysql-cluster 가 쫓겨나지 않음

  • 비정상 mysql 파드는 1개 이상이 될 수 없도록 설정 되어 있다.

# 현재 PDB 정책에 mysql 서버파드는 최대 1개까지만 UNAVAILABLE 비정상 상태로 될 수 있음
# 참고로 PDB 정책에 'MIN AVAILABLE' 과 'MAX AVAILABLE' 는 동시에 두 곳에 설정을 지정할 수 없음
kubectl get pdb -n mysql-cluster
NAME            MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
mycluster-pdb   N/A             1                 0                     49m

만약 PDB 정책을 삭제하게 된다면?

  • 다시 시도 시 쫓겨나서 남은 MySQL 서버 파드가 1대만 존재한다...
kubectl delete pdb -n mysql-cluster mycluster-pdb