Study

[스터디] DOIK - MySQL DB Kubernetes Operator 배포 및 테스트

mokpolar 2022. 6. 5. 14:42
반응형

쿠버네티스 데이터베이스 오퍼레이터 스터디라는 훌륭한 스터디에 참여하게 되었다. 

가시다님이 진행하시는 스터디인데 그전에 명성은 들어보았으나 역시 아주 알차다. 

 

이번 스터디에서는 데이터베이스를 오퍼레이터 패턴으로 쿠버네티스에 배포하고 운영하는 법을 배운다.

이 글은 스터디 1~3주차를 진행하면서 실습한 내용을 기록하기 위한 글이다.

 

쿠버네티스 오퍼레이터란?

쿠버네티스 문서의 오퍼레이터 항목을 읽어보면

오퍼레이터(Operator)는 사용자 정의 리소스 Custom Resource를 사용하여 애플리케이션 및 해당 컴포넌트를 관리하는 쿠버네티스의 소프트웨어 익스텐션이다. 오퍼레이터는 쿠버네티스 원칙, 특히 컨트롤 루프를 따른다.
...
쿠버네티스는 자동화를 위해 설계되었다. 기본적으로 쿠버네티스의 중추를 통해 많은 빌트인 자동화 기능을 사용할 수 있다. 쿠버네티스를 사용하여 워크로드 배포 및 실행을 자동화할 수 있고, 또한 쿠버네티스가 수행하는 방식을 자동화할 수 있다.
쿠버네티스의 오퍼레이터 패턴 개념을 통해 쿠버네티스 코드 자체를 수정하지 않고도 컨트롤러를 하나 이상의 사용자 정의 리소스(custom resource)에 연결하여 클러스터의 동작을 확장할 수 있다. 오퍼레이터는 사용자 정의 리소스의 컨트롤러 역할을 하는 쿠버네티스 API의 클라이언트이다.

 

라고 설명이 되어있다. 

이런 느낌인 것 같다. 

 

오퍼레이터의 동작 예시를 담은 애니메이션을 구글링을 하다가 발견했다. 뭔가 한 눈에 이해할 수 있을 것만 같다. 

0
출처 : https://github.com/inlets/inlets-operator

 

그리고 Operatorhub에서는 많은 Operator의 예시들을 찾을 수 있다. 

 

이런 오퍼레이터를 통해 데이터베이스를 만들어볼 것이다. 

 

실습환경 생성

EKS가 아닌 바닐라 쿠버네티스를 EC2 노드 4대에 올린 뒤 실습을 진행하였다. 

멋지게도 바닐라 쿠버네티스를 생성하기 위한 Cloudformation 생성파일을 스터디에서 미리 준비해주셨다. 

 

바닐라 쿠버네티스의 구조는 아래와 같다. 

조건은 쿠버네티스 버전 v1.23.6 , Flannel CNI(VXLAN 모드, ENI S/D Uncheck) , CRI(containerd), StorageClass(local-path/hostpath, nfs-subpath/EFS)이다. 

Designer에서 구조를 확인하기 위해 AWS Console에서 파일을 업로드해서 확인해보았다. 

AWS Cloudformation에서 템플릿을 로드하면 아래와 같이 보인다. 

 

 

다시 CLI로 돌아와서 위의 Cloudformation 파일을 배포한다. 

aws cloudformation deploy --template-file Cloudformation템플릿파일.yaml --stack-name myk8s --parameter-overrides KeyName=나의Key SgIngressCidr=우리집IP --region ap-northeast-2

 

CloudFormation에서 확인해보면 잘 생성된 모습을 확인할 수 있다. 

 

원격 접속을 하고 준비된 코드로 CNI를 설치해주면 쿠버네티스 구축작업이 끝난다.

그리고 정상작동 확인을 해준다.

kubectl cluster-info
Kubernetes control plane is running at https://192.168.10.10:6443
CoreDNS is running at https://192.168.10.10:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
    Drop-In: /etc/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: active (running) since Sun 2022-06-05 11:53:26 KST; 41min ago
 ..

 

Kubernetes cluster 시각화 툴 kube-ops-view 배포

이 스터디에서 좋은 점중 하나는 각종 힙(?)한 툴들을 소개시켜준다는 점이다. 

아래는 그 중 하나인 kube-ops-view이다.

해당 툴을 쿠버네티스에 배포하고 나면 각 노드의 배포 현황을 알 수 있다. 

이제 준비가 되었으니 MySQL Operator를 배포해보자. 

 

MySQL Operator 배포

MySQL Operator를 배포해보자.

그 아키텍쳐를 살펴보면 아래와 같이 된다. 

 

출처 : https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-introduction.html

해당 프로젝트 깃허브 리포지토리에서 상세한 배포와 테스트 방법을 확인할 수 있다. 

원래 InnoDB Cluster가 가지는 기능과 Kubernetes Operator로서 가지는 기능이 아키텍쳐에 포함되어있다. 

백업, 복구, 스케일링 등은 Operator가 가지는 기능이다. 

 

 

Helm을 통해서 설치하기 위해 아래와 같이 repo를 추가하고 설치를 시작하자. 

helm repo add mysql-operator https://mysql.github.io/mysql-operator/
helm install mysql-operator mysql-operator/mysql-operator --namespace mysql-operator --create-namespace
helm install mycluster mysql-operator/mysql-innodbcluster --set credentials.root.password='####' --set tls.useSelfSigned=true --namespace mysql-cluster --create-namespace

 

InnoDB Cluster를 배포하는 장면이 kube-ops-view로 보면 너무 멋있어서 gif로 만들어봤다. 

InnoDB Cluster의 각 Pod가 노드마다 배치가 되는 모습을 시각화해서 보여준다. 

0

그리고 필요한 컴포넌트들이 다 배포되었는지 확인해본다. 

kubectl get innodbcluster,sts,pod,pvc,svc,pdb,all -n mysql-cluster
NAME                                       STATUS   ONLINE   INSTANCES   ROUTERS   AGE
innodbcluster.mysql.oracle.com/mycluster   ONLINE   3        3           1         2m14s

NAME                         READY   AGE
statefulset.apps/mycluster   3/3     2m14s

NAME                                    READY   STATUS    RESTARTS   AGE
pod/mycluster-0                         2/2     Running   0          2m14s
pod/mycluster-1                         2/2     Running   0          2m14s
pod/mycluster-2                         2/2     Running   0          2m14s
pod/mycluster-router-5f9758dc8f-pwmfp   1/1     Running   0          77s

NAME                                        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/datadir-mycluster-0   Bound    pvc-7b1e61fd-edaa-412b-9427-8890ec268430   2Gi        RWO            local-path     2m14s
persistentvolumeclaim/datadir-mycluster-1   Bound    pvc-ee34ffbd-4db6-442f-aab3-f95757daa684   2Gi        RWO            local-path     2m14s
persistentvolumeclaim/datadir-mycluster-2   Bound    pvc-97ff25f0-2c85-4dc1-939c-c983a158e6dc   2Gi        RWO            local-path     2m14s

NAME                          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                  AGE
service/mycluster             ClusterIP   ###.###.###    <none>        3306/TCP,33060/TCP,6446/TCP,6448/TCP,6447/TCP,6449/TCP   2m14s
service/mycluster-instances   ClusterIP   None           <none>        3306/TCP,33060/TCP,33061/TCP                             2m14s

NAME                                       MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
poddisruptionbudget.policy/mycluster-pdb   N/A             1                 1                     2m14s

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mycluster-router   1/1     1            1           2m14s

NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/mycluster-router-5f9758dc8f   1         1         1       2m14s

 

Operator로 배포한 MySQL DB 사용

마스터노드에서 MySQL DB에 접근하기 위해 클라이언트를 설치한다.

apt install mariadb-client -y
MYSQLIP=$(kubectl get svc -n mysql-cluster mycluster -o jsonpath={.spec.clusterIP})
mysql -h $MYSQLIP -uroot -p**** -e "SELECT @@hostname;SELECT @@max_connections;"
+-------------+
| @@hostname  |
+-------------+
| mycluster-0 |
+-------------+
+-------------------+
| @@max_connections |
+-------------------+
|               151 |
+-------------------+

데이터가 잘 들어가는지 테스트를 해보기 위해 샘플 데이터를 갖고오고 이런저런 테스트도 해보자. 

git clone https://github.com/datacharmer/test_db && cd test_db/

# 다운로드 받은 데이터를 DB에 추가
mysql -h $MYSQLIP -uroot -p**** -t < employees.sql

# 확인
mysql -h $MYSQLIP -uroot -p**** -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 |
+--------+------------+------------+-----------+--------+------------+


# 간단한 데이터베이스 생성
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;

 

Operator로서의 기능 테스트

여기까지 보면 Kubernetes에 Operator로서 배포된 데이터베이스로서의 면모를 아직 볼 수가 없다. 

그러니 2가지 테스트를 해보겠다. 

 

반복적으로 데이터 INSERT시 데이터 복제 확인

MySQL InnoDB Cluster는 완벽한 고가용성 솔루션을 제공한다고 한다.

최소 "3개"로 이루어진 MySQL 서버 인스턴스 그룹을 구성하고 각 인스턴스는 MySQL Group Replication을 수행하여 서로 데이터를 복제한다. 

Single Primary Mode에서는 하나의 RW가 가능한 Primary 인스턴스를 갖게 된다. 그리고 Primary가 다운될 경우, Secondary는 자동적으로 Primary로 승격된다. 

 

아래와 같이 현재 인스턴스들의 역할을 확인해보자. 0번 인스턴스가 Primary라는 사실을 알 수 있다.  

select member_host,member_role FROM performance_schema.replication_group_members;
+-----------------------------------------------------------------+-------------+
| member_host                                                     | member_role |
+-----------------------------------------------------------------+-------------+
| mycluster-0.mycluster-instances.mysql-cluster.svc.cluster.local | PRIMARY     |
| mycluster-2.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
| mycluster-1.mycluster-instances.mysql-cluster.svc.cluster.local | SECONDARY   |
+-----------------------------------------------------------------+-------------+
3 rows in set (0.002 sec)

 

mysql을 사용해서 데이터베이스를 모니터링할 테스트용 Pod를 생성한다.

apiVersion: v1
kind: Pod
metadata:
  name: mysqlclient-1
  labels:
    app: myclient
spec:
  containers:
  - name: mysqlclient-1
    image: mysql:8.0.29
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0

 

이렇게 테스트용 Pod를 3개 생성한 후 3개의 터미널에서 각각의 Pod에서 InnoDB Cluster를 구성하는 MySQL Pod 3개에 각각 직접 접근하여 모니터링을 한다.

watch -d "kubectl exec -it mysqlclient-0 -- mysql -h mycluster-0.mycluster-instances.mysql-cluster.svc -uroot -p**** -e 'USE test;SELECT * FROM t1 ORDER BY c1 DESC LIMIT 5;'"
watch -d "kubectl exec -it mysqlclient-1 -- mysql -h mycluster-1.mycluster-instances.mysql-cluster.svc -uroot -p**** -e 'USE test;SELECT * FROM t1 ORDER BY c1 DESC LIMIT 5;'"
watch -d "kubectl exec -it mysqlclient-2 -- mysql -h mycluster-2.mycluster-instances.mysql-cluster.svc -uroot -p**** -e 'USE test;SELECT * FROM t1 ORDER BY c1 DESC LIMIT 5;'"

 

그리고 데이터 INSERT를 시작한다.

for ((i=101; i<=1000; i++)); do mysql -h $MYSQLIP -uroot -p**** -e "SELECT @@HOSTNAME;USE test;INSERT INTO t1 VALUES ($i, 'Luis$i');";echo; done

각 Pod에 데이터가 잘 복제되는 모습을 아래와 같이 볼 수 있다. 

 

0

Failover 테스트

Pod가 죽거나 Node가 죽었을 때 정상적인 작동이 되는지를 테스트 해보겠다. 

위에서 확인했듯이 0번 Pod가 현재의 Primary이고, 이 인스턴스가 죽어도 새로운 Primary가 잘 승격되고 데이터 복제가 이루어지는지를 확인해보겠다. 

 

먼저 인스턴스들의 상태를 모니터링 하면서,

watch -d "kubectl exec -it mysqlclient-2 -- mysql -h mycluster-2.mycluster-instances.mysql-cluster.svc -uroot -p**** -e 'select member_host,member_role FROM performance_schema.replication_group_members;'"

 

직전에 했던 것과 같이 그리고 오랫동안 데이터를 입력하고

for ((i=1001; i<=5000; i++)); do mysql -h $MYSQLIP -uroot -p**** -e "SELECT @@HOSTNAME;USE test;INSERT INTO t1 VALUES ($i, 'Luis$i');";echo; done

 

그리고 0번 Primary를 죽이자.

kubectl delete pod -n mysql-cluster mycluster-0 && kubectl get pod -n mysql-cluster -w

 

인위적으로 Primary가 되는 Pod를 죽이자 Secondary였던 2번이 Primary로 승격되고 데이터 입력도 정상적으로 이루어지는 모습을 볼 수 있다. 

 

0

 

마무리

그전까지는 데이터베이스를 쿠버네티스에 올린다는 사실에 대해서 막연하게, 그리고 어디서 풍문으로 들은 바로..는 어렵지 않을까라는 생각을 해왔었다. 

 

하지만 실제로 학습을 진행하고 여러 발표자들이 Production 레벨에서 쿠버네티스에서 데이터베이스를 운영하는 모습을 보니 이것도 택할 수 있는 길이 될 수 있겠다는 생각이 들었다. 

 

스터디를 진행하면서 추가로 더 많은 실습을 진행해보고 쿠버네티스 상에서의 오퍼레이터 패턴 데이터베이스에 대해 더 깊게 알아볼 생각이다. 

 

Reference

반응형