티스토리 뷰
[Kubernetes - Operator] KUDO를 활용한 Galera Operator 단계별로 적용하기 - PART 3: Bootstrap 삭제 및 서비스 중단 없이 Scale Up/Down 처리
ccambo 2021. 1. 5. 19:24How to building real world sample step by step - Part 3
게시글 참고
이 게시글은 KUDO Blog 샘플을 기준으로 테스트하면서 발생한 문제점들을 처리하고 동작을 검증하면서 정리한 내용입니다.
이 문서는 KUDO Blog 샘플을 기준으로 테스트하면서 발생한 문제점들을 해결하고 동작을 검증하면서 정리한 내용으로 구성된 Galera Cluster의 사용하지 않는 bootstrap 정보를 제거하고, 외부 연결을 위한 서비스 생성 및 서비스의 중단없이 Scale Up/Down 할 수 있도록 나머지 부분을 적용해 본다. 이 과정까지 완료되면 프로덕션 환경에 적용할 수 있는 정도가 된다.
Cleanup bootstrap node
PART 2 에서 모든 노드들을 클러스터에 참여시켰기 때문에 더 이상은 부트스트랩 노드가 필요하지 않다.
따라서 operator.yaml
에 부트스트랩 노드와 관련된 리소스를 제거하는 Step과 Task를 추가하도록 한다.
...
plans:
deploy:
strategy: serial
phases:
- name: deploy
strategy: serial
steps:
...
- name: cleanup # 추가
tasks:
- bootstrap_cleanup
tasks:
...
- name: bootstrap_cleanup # 추가
kind: Delete
spec:
resources:
- bootstrap_deploy.yaml
- bootstrap_service.yaml
- bootstrap_config.yaml
추가된 bootstrap_cleanup
은 삭제 작업으로 PART 1에서 만들었던 bootstrap 관련 리소스들이 대상이며 이 작업이 실행되면 resources로 지정된 *.yaml 파일에 정의된 부트스트랩 리소스들 (ConfigMap, Service, Deploy)이 모두 삭제된다.
그러나 실제 Galera 노드들에서 사용하는 ConfigMap에 부트스트랩 노드 정보 (wsrep_cluster_address = gcomm://{{ .Name }}-bootstrap-svc)
가 남아 있기 떄문에 Galera 인스턴스가 누락된 노드를 중심으로 작동하므로 반드시 문제가 되는 것은 아닐지라도 완전성을 위해 관련된 정보도 삭제해 주는 것이 좋다.
따라서 opreator.yaml
파일에 ConfigMap을 수정할 수있는 Task를 추가하고 Step에서 이 Task를 이용하는 것으로 아래와 같이 변경한다.
...
plans:
deploy:
strategy: serial
phases:
- name: deploy
strategy: serial
steps:
...
- name: cleanup
tasks:
- bootstrap_cleanup
- config # 추가
tasks:
...
- name: bootstrap_cleanup
kind: Delete
spec:
resources:
- bootstrap_deploy.yaml
- bootstrap_service.yaml
- bootstrap_config.yaml
- name: config # 추가
kind: Apply
spec:
resources:
- galera_config.yaml
이번에는 하나의 Step에서 두 개의 Task가 동작하도록 정의했다. deploy phase의 실행 전략을 strategy: serial
로 지정했으므로 두 개의 Task는 순차적으로 처리된다.
참고
여기서 주의할 것은 PART 2에서 사용했던
galera_config.yaml
을 다시 사용한다는 것이다. 즉, 노드를 설치할 때와 삭제할 때 설정 기능이 다른데 하나의 리소스를 재 사용하는 상황인 것이다. 이를 해결하기 위해서 템플릿에서.StepName
변수를 활용해서 어떤 Step에서 호출되었는지를 식별해서 처리하는 방식을 사용한다.
StepName 정보를 기준으로 내용을 구성
할 수 있도록 템플릿 리소스인 templates/galera_config.yaml
파일을 아래와 같이 수정한다.
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Name }}-nodeconfig
namespace: {{ .Namespace }}
data:
galera.cnf: |
[galera]
wsrep_on = ON
wsrep_provider = /usr/lib/galera/libgalera_smm.so
wsrep_sst_method = mariabackup
{{ if eq .StepName "firstboot_config" -}}
wsrep_cluster_address = gcomm://{{ .Name }}-bootstrap-svc,{{ $.Name }}-{{ $.OperatorName }}-0.{{ $.Name }}-hs{{ range $i, $v := untilStep 1 (int .Params.NODE_COUNT) 1 }},{{ $.Name }}-{{ $.OperatorName }}-{{ $v }}.{{ $.Name }}-hs{{end}}
{{ else -}}
wsrep_cluster_address = gcomm://{{ $.Name }}-{{ $.OperatorName }}-0.{{ $.Name }}-hs{{ range $i, $v := untilStep 1 (int .Params.NODE_COUNT) 1 }},{{ $.Name }}-{{ $.OperatorName }}-{{ $v }}.{{ $.Name }}-hs{{end}}
{{ end -}}
wsrep_sst_auth = "{{ .Params.SST_USER }}:{{ .Params.SST_PASSWORD }}"
binlog_format = ROW
innodb.cnf: |
[innodb]
innodb_autoinc_lock_mode = 2
innodb_flush_log_at_trx_commit = 0
innodb_buffer_pool_size = 122M
wsrep_cluster_address
정보를 지정할 때 템플릿에 조건부 처리를 지정했다. 즉, firstboot_config
Step일 경우는 부트스트랩 구성이 적용되고, 그 외는 클러스터에 참여할 노드 정보들만 구성되는 것이다.
{{ if eq .StepName "firstboot_config" -}}
wsrep_cluster_address = gcomm://{{ .Name }}-bootstrap-svc,{{ $.Name }}-{{ $.OperatorName }}-0.{{ $.Name }}-hs{{ range $i, $v := untilStep 1 (int .Params.NODE_COUNT) 1 }},{{ $.Name }}-{{ $.OperatorName }}-{{ $v }}.{{ $.Name }}-hs{{end}}
{{ else -}}
wsrep_cluster_address = gcomm://{{ $.Name }}-galera-0.{{ $.Name }}-hs{{ range $i, $v := untilStep 1 (int .Params.NODE_COUNT) 1 }},{{ $.Name }}-galera-{{ $v }}.{{ $.Name }}-hs{{end}}
{{ end -}}
if
, else
, end
구문은 -}}
으로 종료시키고 있다. 이는 템플릿 엔진에게 해당 라인을 빈줄로 뇌두지 말고 삭제하도록 지정하는 것이다.
위와 같이 템플릿 내에 조건문을 사용해서 다른 여러 Task들에서 재 사용할 수 있다.
이제 Pod가 재 시작되면 post-bootstrap cluster에 대한 올바른 구성을 가지고 다시 백업을 시작할 수 있게 된다.
지금까지 작업한 것을 기준으로 정상적으로 동작하는지 테스트를 하도록 한다.
KUDO 설치 (using init)
$ kubectl kudo init --unsafe-self-signed-webhook-ca --wait # Webhook TLS를 자체 서명한 버전으로 처리
Galera Operator 설치 (using install)
$ kubectl kudo install ./
설치된 Operator Instance 확인
$ kubectl kudo plan status --instance=galera-operator-instance
설치된 ConfigMap 및 Pod 확인
$ kubectl describe configmap galera-operator-instance-nodeconfig Name: galera-operator-instance-nodeconfig Namespace: default Labels: heritage=kudo kudo.dev/instance=galera-operator-instance kudo.dev/operator=galera-operator Annotations: kudo.dev/last-applied-configuration: {"kind":"ConfigMap","apiVersion":"v1","metadata":{"name":"galera-operator-instance-nodeconfig","namespace":"default","creationTimestamp":n... kudo.dev/last-plan-execution-uid: 8cd36d4b-aeb5-473e-8997-1fad6090722a kudo.dev/phase: deploy kudo.dev/plan: deploy kudo.dev/step: cleanup Data ==== galera.cnf: ---- [galera] wsrep_on = ON wsrep_provider = /usr/lib/galera/libgalera_smm.so wsrep_sst_method = mariabackup wsrep_cluster_address = gcomm://galera-operator-instance-galera-0.galera-operator-instance-hs,galera-operator-instance-galera-1.galera-operator-instance-hs,galera-operator-instance-galera-2.galera-operator-instance-hs wsrep_sst_auth = "root:admin" binlog_format = ROW innodb.cnf: ---- [innodb] innodb_autoinc_lock_mode = 2 innodb_flush_log_at_trx_commit = 0 innodb_buffer_pool_size = 122M Events: <none>
정상적으로 ConfigMap이 조건에 따라서
wsrep_cluster_address
설정 값이 부트스트랩 정보없이 노드들의 정보만으로 구성된 것을 확인할 수 있다.$ kubectl get pods NAME READY STATUS RESTARTS AGE galera-operator-instance-galera-operator-0 1/1 Running 0 3m28s galera-operator-instance-galera-operator-1 1/1 Running 0 3m3s galera-operator-instance-galera-operator-2 1/1 Running 0 2m35s nfs-client-provisioner-b84668c6d-zxt8d 1/1 Running 0 91m
부트스트랩 노드가 제거되고 나머지 노드들만 존재하는 것을 확인할 수 있다.
KUDO 및 Operator 삭제
$ kubectl kudo init --unsafe-self-signed-webhook-ca --upgrade --dry-run --output yaml | kubectl delete -f -
Add service for connect from client
이제 클라이언트가 Galera Cluster에 연결할 수 있는 방법이 필요하다. Galera Cluster는 다중 마스터 구조이기 때문에 어디로 연결되더라도 안전하게 읽고 쓸 수 있다. 서비스는 어떤 의존성도 갖지않기 때문에 deploy plan의 어떤 단계에서 생성하던지 상관이 없지만 이미 클러스터 내부의 서비스를 생성한 단계가 있으므로 여기에 추가하도록 한다.
operator.yaml
파일의 이미 존재하는 cluster_service
Task에 아래와 같이 추가한다.
...
- name: cluster_services
kind: Apply
spec:
resources:
- hs-service.yaml
- cs-service.yaml # 추가
...
이제 사용한 서비스 리소스인 templates/cs-service.yaml
파일을 생성하고 아래와 같이 구성한다.
apiVersion: v1
kind: Service
metadata:
name: {{ .Name }}-cs
namespace: {{ .Namespace }}
labels:
app: galera
galera: {{ .Name }}
spec:
ports:
- port: {{ .Params.MYSQL_PORT }}
name: mysql
selector:
app: galera
instance: {{ .Name }}
이 서비스는 mySQL 포트만 필요하고 클라이언트가 Galera Clustr에 연결할 수 있도록 load balancing된 ClusterIP를 제공
한다.
지금까지 작업한 것을 기준으로 정상적으로 동작하는지 테스트를 하도록 한다.
KUDO 설치 (using init)
$ kubectl kudo init --unsafe-self-signed-webhook-ca --wait # Webhook TLS를 자체 서명한 버전으로 처리
Galera Operator 설치 (using install)
$ kubectl kudo install ./
설치된 Operator Instance 확인
$ kubectl kudo plan status --instance=galera-operator-instance
설치된 Service 확인
$ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE galera-operator-instance-cs ClusterIP 10.104.170.201 <none> 3306/TCP 5m3s galera-operator-instance-hs ClusterIP None <none> 3306/TCP,4444/TCP,4567/TCP,4568/TCP 5m3s
실행 중인 클러스터에 모든 서비스가 생성된 것을 확인할 수 있다. 서비스의 상세 정보를 통해서 정상적으로 생성되었는지 확인한다.
$ kubectl describe service galera-operator-instance-cs Name: galera-operator-instance-cs Namespace: default Labels: app=galera galera=galera-operator-instance heritage=kudo kudo.dev/instance=galera-operator-instance kudo.dev/operator=galera-operator Annotations: kudo.dev/last-applied-configuration: {"kind":"Service","apiVersion":"v1","metadata":{"name":"galera-operator-instance-cs","namespace":"default","creationTimestamp":null,"label... kudo.dev/last-plan-execution-uid: f5b71f8e-b9ef-49b0-81d1-8f82315e63d4 kudo.dev/phase: deploy kudo.dev/plan: deploy kudo.dev/step: cluster_services Selector: app=galera,instance=galera-operator-instance Type: ClusterIP IP: 10.104.170.201 Port: mysql 3306/TCP TargetPort: 3306/TCP Endpoints: 10.244.247.12:3306,10.244.84.132:3306 Session Affinity: None Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedToUpdateEndpointSlices 4m37s endpoint-slice-controller Error updating Endpoint Slices for Service default/galera-operator-instance-cs: failed to update galera-operator-instance-cs-brmb9 EndpointSlice for Service default/galera-operator-instance-cs: Operation cannot be fulfilled on endpointslices.discovery.k8s.io "galera-operator-instance-cs-brmb9": the object has been modified; please apply your changes to the latest version and try again
- 클라이언트 연결 확인
생성한 서비스를 통해서 클라이언트가 Galera Cluster에 접속할 수 있다.
$ kubectl run mysql-client --image=mysql:5.7 -it --rm --restart=Never -- mysql -u root -h galera-operator-instance-cs -p
진행 중에 아래와 같은 메시지가 보이면
params.yaml
에 정의했던 password를 입력하면 된다.If you don't see a command prompt, try pressing enter.
mysql>
프롬프트가 보이면 정상적으로 연결이 된 것이다.KUDO 및 Operator 삭제
$ kubectl kudo init --unsafe-self-signed-webhook-ca --upgrade --dry-run --output yaml | kubectl delete -f -
Scale up/down without service interruption
KUDO는 파라미터의 값을 변경함으로써 연계된 Plan 이 구동된다. 파라미터로 정의한 NODE_COUNT
를 변경하면 deploy plan이 구동될 것이기 때문에 ConfigMap을 변경하고 클러스터에 새로운 노드 수를 반영하기 위해서 StatefulSet이 재 시작될 필요가 있다.
이런 작업을 반영하기 위해 operator.yaml
파일에 새로운 plan을 아래와 같이 추가한다.
...
plans:
deploy:
...
node_update: # 추가
strategy: serial
phases:
- name: deploy
stratege: serial
steps:
- name: config
tasks:
- config
- name: stateful
tasks:
- statefulset
...
이 Plan은 기존의 Task를 재 사용하는 것이기 때문에 별도의 Task 추가는 필요하지 않고 두 개의 Step이 순차적으로 실행되도록 정의한 것이다.
이제 NODE_COUNT
파라미터가 변경되면 위에 정의한 새로운 Plan이 구동될 수 있도록 params.yaml
의 파라미터 정보를 아래와 같이 추가한다.
apiVersion: kudo.dev/v1beta1
parameters:
...
- name: NODE_COUNT
description: "Number of nodes to create in the cluster"
default: "3"
trigger: node_update # 추가
...
PART 2 에서는 trigger
정보가 없었으며 이런 경우는 기본 값으로 deploy
plan이 호출되게 된다. trigger를 지정해서 node_update
plan을 호출하도록 한 것이다.
지금까지 작업한 것을 기준으로 정상적으로 동작하는지 테스트를 하도록 한다.
KUDO 설치 (using init)
$ kubectl kudo init --unsafe-self-signed-webhook-ca --wait # Webhook TLS를 자체 서명한 버전으로 처리
Galera Operator 설치 (using install)
$ kubectl kudo install ./
설치된 Operator Instance 확인
$ kubectl kudo plan status --instance=galera-operator-instance
NODE_COUNT 변경 검증
$ kubectl kudo update --instance galera-operator-instance -p NODE_COUNT=3
파라미터를 통한 변경이 적용되었기 때문에 변경에 따른 처리를 확인한다.
$ kubectl kudo plan status --instance=galera-operator-instance
Pod가 재 시작되는 시간이 있기 때문에
IN_PROGRESS
상태로 나타나며, 이 작업이 진행 중인 동안에kubectl get pods
명령을 수행하면 추가된 수 만큼 노드가 배포가 되고, 이전에 동작하던 노드들은 한번에 하나씩 재 시작되는 것을 볼 수 있다. 반대로NODE_COUNT
파라미터의 수를 줄이면 동작하던 노드들이 제거되는 것을 확인할 수 있다.KUDO 및 Operator 삭제
$ kubectl kudo init --unsafe-self-signed-webhook-ca --upgrade --dry-run --output yaml | kubectl delete -f -
Process syncronization before shuting down nodes
Scale up은 상관이 없지만 down의 경우는 노드가 제거될 때 Galera Cluster의 노드가 동기화되지 않으면 클러스터의 상태에 문제가 발생할 수 있으므로 노드가 종료되기 전에 동기화를 해야할 필요가 있다.
이를 위해서 상태를 검증하는 스크립트를 추가하고 StatefulSet의 spec에 preStop check를 수정해서 처리를 수행하도록 operator.yaml
파일의 deploy plan의 Statefulset Step 이전
에 적용될 수 있도록 Step과 Task를 추가한다.
...
plans:
deploy:
strategy: serial
phases:
- name: deploy
strategy: serial
steps:
...
- name: node_scripts # 추가
tasks:
- node_scripts
...
- name: statefulset
...
...
tasks:
...
- name: node_scripts # 추가
kind: Apply
spec:
resources:
- node_scripts.yaml
상태를 검증하는 스크립트를 위한 ConfigMap 리소스인 templates/node_scripts.yaml
파일을 생성하고 아래와 같이 구성한다.
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Name }}-node-scripts
namespace: {{ .Namespace }}
wait-for-sync.sh: |
until mysql -u root -p{{ .Params.MYSQL_ROOT_PASSWORD }} -e "SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';" | grep -q Synced
do
echo "Waiting for sync"
sleep 5
done
그리고 이 스크립트가 StatefulSet에 정상적으로 마운트될 수 있도록 templates/statefulset.yaml
에 추가한다.
volumeMounts:
- name: {{ .Name }}-config
mountPath: /etc/mysql/conf.d
- name: {{ .Name }}-datadir
mountPath: /var/lib/mysql
- name: {{ .Name }}-node-scripts # 추가
mountPath: /etc/galera/wait-for-sync.sh
subPath: wait-for-sync.sh
volumes:
- name: {{ .Name }}-config
configMap:
name: {{ .Name }}-nodeconfig
items:
- key: galera.cnf
path: galera.cnf
- key: innodb.cnf
path: innodb.cnf
- name: {{ .Name }}-node-scripts # 추가
configMap:
name: {{ .Name }}-node-scripts
defaultMode: 0755
ConfigMap에서 subPath로 스크립트을 직접 마운트했다. subPath를 사용할 때 제한들이 있지만 여기서는 큰 의미가 없다.
또한 Kubernetes 클러스터를 구성하는 노드들의 장애로 부터 보호하기 위해서 분리된 노드들로 배포되도록 스케줄되어 있는지에 대한 확인이 필요하다. 이 작업은 AntiAffinity (반 선호도) 규칙을 사용해서 처리가 가능하며 테스트를 위해서 이 규칙을 비활성화 시킬 수 있다. templates/statefulset.yaml
파일에 아래와 같이 조건 섹션을 구성한다.
...
spec:
{{ if eq .Params.ANTI_AFFINITY "true" }}
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- galera
- key: "instance"
operator: In
values:
- {{ .Name }}
topologyKey: "kubernetes.io/hostname"
{{ end }}
...
참고
상기 예제의 Anti-Affinity는 반 선호도에 대한 Pod Selector 개념으로
topologyKey를 kubernetes.io/hostname으로 설정해서 Pod가 존재하는 동일한 Host가 아닌 다른 곳으로 배치
하나는 의미가 된다.
사용된 ANTI_AFFINITY
파라미터를 params.yaml
파일에 추가한다.
...
- name: ANTI_AFFINITY
description: "Enforce pod anit-affinity"
default: False
...
지금까지의 작업으로 Operator는 Node 선호도를 관리하고 안전하게 Scale up/down이 가능하고, 외부의 클라이언트가 접근할 수 있도록 구성되었다.
이 시점에서 몇 가지 벤치마크와 클러스터를 확장하고 축소하는 동시에 모든 것이 정상적으로 작동하는지를 확인해야 한다. how to measure mysql performance in kubernetes with sysbench를 이용해서 확인이 가능하다.
Conclusion
지금까지 Galera Cluster를 구성하고 운영하는 Operator를 단계별로 확인해 보았으며, 그 과정을 통해서 KUDO에서 제공하는 내장 변수들과 Sprig 템플릿 함수를 이용해서 조건부 템플릿을 적용해 리소스 파일들을 재 사용할 수 있다는 것도 확인했다.
향후에는 시간이 허락하고 관련된 작업을 진행하게 된다면 지금까지 확인한 설치관련 (Day 1 Operation) 뿐만 아니라 운영 중 (Day 2 Operation) 에 발생할 수 있는 상황들에 대한 부분을 추가로 검토해 볼 예정이다.
참고 자료
'개발 > Kubernetes 이해' 카테고리의 다른 글
- Total
- Today
- Yesterday
- dynamic nfs client provisioner
- SolrCloud
- macos
- Cluster
- operator
- operator framework
- custom resource
- opencensus
- NFS
- GIT
- Kubernetes
- Packages
- k8s
- Replica
- galera
- KUBECTL
- CentOS 8
- ssh
- zookeeper
- docker
- kudo-cli
- 쿠버네티스
- Galera Cluster
- leader
- Node
- terrminating
- provisioner
- collection
- CentOS
- Kudo
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |