기본 콘텐츠로 건너뛰기

[Kubernetes - Operator] KUDO를 활용한 Galera Operator 단계별로 적용하기 - PART 2: Nodes 참여와 서비스 생성 및 StatefulSet 구성

How to building real world sample step by step - Part 2

게시글 참고

이 게시글은 KUDO Blog 샘플을 기준으로 테스트하면서 발생한 문제점들을 처리하고 동작을 검증하면서 정리한 내용입니다.

  • PART 1에서는 부트스트랩 노드 구성
  • PART 3에서는 사용하지 않는 부트스트랩을 제거하고, 외부 접속을 위한 서비스를 생성하며, 안전한 Scale Up/Down 처리 구성

이 문서는 KUDO Blog 샘플을 기준으로 테스트하면서 발생한 문제점들을 해결하고 동작을 검증하면서 정리한 내용으로 초기 구성된 Galera Cluster의 부트스트랩을 이용해서 클러스터에 노드들이 참여할 떄 사용할 서비스와 설정등을 구성하고 StatefulSet을 구성하는 방법을 정리한다.

Cluster에 참여하는 Node를 위한 초기 설정 구성

operator.yaml 파일에 아래와 같이 firstboot_config라는 Step과 Task를 추가하도록 한다.

apiVersion: kudo.dev/v1beta1
appVersion: 0.1.0
kubernetesVersion: 0.16.0
kudoVersion: 0.17.2
maintainers:
- email: ccambo@gmail.com
  name: ccambo
name: galera-operator
operatorVersion: 0.1.0
plans: 
  deploy:
    strategy: serial
    phases:
      - name: deploy
        strategy: serial
        steps:
          - name: bootstrap_config
            tasks:
              - bootstrap_config
          - name: bootstrap_service
            tasks:
              - bootstrap_service
          - name: bootstrap_deploy
            tasks:
              - bootstrap_deploy
          - name: firstboot_config    # 추가
            tasks:
              - firstboot_config
tasks:
  - name: bootstrap_config
    kind: Apply
    spec:
      resources:
        - bootstrap_config.yaml
  - name: bootstrap_service
    kind: Apply
    spec:
      resources:
        - bootstrap_service.yaml
  - name: bootstrap_deploy
    kind: Apply
    spec:
      resources:
        - bootstrap_deploy.yaml
  - name: firstboot_config      # 추가
    kind: Apply
    spec:
      resources:
        - galera_config.yaml

참고

위의 예제에서는 deploy Phase에서 개별적인 여러 개의 Step들을 정의했고, 이 Step들은 KUDO에 의해서 순차적으로 실행된다.
작업한 몇 개의 Step들은 서로 결과를 공유하는 등의 의존성이 없고, 이론적으로는 하나의 Step내에 여러 개의 Task를 지정해서 처리하는 것도 가능하고 Phase 자체를 병렬로 처리하게 하는 것도 가능하다.

위의 firstboot_config Task에서 사용할 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
    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}}
    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

노드가 Galera Cluster에 참여할 때 처리되어야 하는 두 개의 설정을 포함하는 ConfigMap을 구성한 것이다.

  • galera.cnf: Galera Cluster와 연동을 위한 설정
  • innodb.cnf: Galera 사용할 시에 InnoDB Engine에 권장되는 설정

Galera Cluster에 추가될 노드는 부트스트랩 노드의 서비스 주소와 클러스터에 포함될 노드들의 주소 정보가 필요하다.

wsrep_cluster_address 설정은 KUDO에서 지원하는 Sprig 템플릿 방식을 사용해서 다음과 같이 표현된다.

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}}
  • 노드들은 StatefulSet이 될 것이기 때문에 파드의 이름을 .Name-.OperatorName-number 형식이 될 것으로 유추할 수 있다. 여기서 .Name과 .OperatorName은 KUDO에서 생성된 변수이고 번호는 인스턴스화된 StatefulSet의 Replicas의 순서에 기반해서 Kubernetes가 할당한 값이 된다.
  • 노드들에 접속하기 위한 Headless 서비스를 만들고 유일함을 보장하기 위해 .Name-hs 형식의 이름을 가질 것이다. 뒤에서 다시 언급할 것이기는 하지만 StatefulSet의 각 노드들에 대한 DNS 항목들은 예측 가능한 .Name-OperatorName-number.Name-hs 포맷의 이름이 된다.

위와 같은 가정을 기준으로 연결 문자열을 구성할 수 있다.

우선 부트스트랩 노드의 서비스 주소를 추가하고 다음 연결 문자열을 위해 ',' 로 연계한다.

gcomm://{{ .Name }}-bootstrap-svc,

노드는 최소한 1개 이상이 존재해야 하기 때문에 0 순번의 노드가 고정되어 있는다는 것을 알기 때문에 DNS 항목을 추가할 수 있다.

gcomm://{{ .Name }}-bootstrap-svc, {{ $.Name }}-{{ $.OperatorName }}-0.{{ $.Name }}-hs

Galera Cluster에 참여할 노드들을 구성하기 위한 NODE_COUNT 파라미터를 params.yaml파일에 아래와 같이 가장 마지막에 추가한다.

apiVersion: kudo.dev/v1beta1
parameters:
  ...
  - name: NODE_COUNT
    description: "Number of nodes to create in the cluster"
    default: "3"

그 다음은 params.yaml 에 정의된 NODE_COUNT 파라미터의 값을 참고해서 나머지 노드들을 처리할 수 있다. 이를 활용하는 Sprig 템플릿은 아래와 같다.

{{ range $i, $v := untilStep 1 (int .Params.NODE_COUNT) 1 }}, {{ $.Name }}-{{ $.OperatorName }}-{{ $v }}.{{ $.Name }}-hs{{ end }}

위의 내용은 표준 Go 템플릿의 Range 함수를 사용해서 인덱스 ($i)와 값 ($v)을 제공하고 Sprig의 untilStep 함수를 사용해서 생성된 범위 값을 전달한다. 이를 통해서 1 부터 시작해서 NODE_COUNT 까지 1 단위로 증가한 정수 목록을 받아서 사용하게 된다. NODE_COUNT가 3이라면 정수 범위는 1, 2 가 된다.

주의

  • params.yaml 정의된 NODE_COUNT 파라미터는 문자열로 기본처리가 되므로 사용하기 전에 (int .Params.NODE_COUNT)와 같이 int 형식으로 캐스팅해 줘야 한다.
  • Ranage 함수가 호출되면 변수들이 함수 내로 Scope 조정이 되기 때문에 최 상위 컨텍스트를 참조하기 위해서는 $ 접두사를 이용해서 파라미터들에 접근해야 한다.

위의 템플릿 범위 함수를 통해서 ','로 분리되는 형식의 클러스터의 모든 노드들에 대한 DNS 엔트리를 구성할 수 있다.

참고

복잡한 템플릿을 작업을 하는 것은 상당히 어려울 수 있기 때문에 테스트를 수행할 수 있는 go 코드를가지고 검증하는 것이 유용할 수 있다. 따라서 Template example 과 같이 템플릿을 stdout으로 출력하는 것과 같은 코드를 잘 활용하는 것이 좋다.

구성된 KUDO Package를 kubectl kudo package verify 명령을 통해서 논리적인 오류는 검증할 수 없겠지만 구문 오류 등을 확인할 수는 있다.

KUDO CLI의 Package 관련 명령을 통해서 새로운 파라미터들과 Task들이 올바르게 정의되었는지 등의 YAML 구성에 대한 검증도 가능하다.

  • Package Plans 구성 검증

    $ kubectl kudo package list plans ./    # Operator 상대 경로 또는 Package 명 지정 (여기서는 로컬 디렉터리를 사용하므로 ~/kudo_sample/galera-operator/operator 경로 사용)
    

    plans └── deploy (serial) └── [phase] deploy (serial) ├── [step] bootstrap_config │ └── bootstrap_config ├── [step] bootstrap_service │ └── bootstrap_service ├── [step] bootstrap_deploy │ └── bootstrap_deploy └── [step] firstboot_config └── firstboot_config

  • Package Tasks 구성 검증

    $ kubectl kudo package list tasks ./
    

    Name Kind Resources
    bootstrap_config Apply [bootstrap_config.yaml] bootstrap_service Apply [bootstrap_service.yaml] bootstrap_deploy Apply [bootstrap_deploy.yaml] firstboot_config Apply [galera_config.yaml]

  • Package Paramters 구성 검증

    $ kubectl kudo package list parameters ./
    

    Name Default Required Immutable IST_PORT 4568 true false
    MYSQL_PORT 3306 true false
    MYSQL_ROOT_PASSWORD admin true false
    REPLICATION_PORT 4567 true false
    SST_PASSWORD admin true false
    SST_PORT 4444 true false
    SST_USER root true false

위의 검증을 통해서 올바르게 설정된 것이 확인 되었다면 지금까지 작업한 것을 기준으로 정상적으로 동작하는지 테스트를 하도록 한다.

  • 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 확인

    새로운 Step과 Task인 firstboot_config을 통해서 템플릿을 활용한 ConfigMap이 생성되었을 것이므로 템플릿이 제대로 동작했는지를 아래와 같이 확인해 본다.

    $ kubectl get configmaps
    

    NAME DATA AGE galera-operator-instance-bootstrap 1 27s galera-operator-instance-nodeconfig 2 12s

    $ 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: 64db8c01-d078-404d-8344-1a6bd98733fe kudo.dev/phase: deploy kudo.dev/plan: deploy kudo.dev/step: firstboot_config

    Data

    innodb.cnf:

    [innodb] innodb_autoinc_lock_mode = 2 innodb_flush_log_at_trx_commit = 0 innodb_buffer_pool_size = 122M

    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-bootstrap-svc,galera-operator-instance-galera-operator-0.galera-operator-instance-hs,galera-operator-instance-galera-operator-1.galera-operator-instance-hs,galera-operator-instance-galera-operator-2.galera-operator-instance-hs wsrep_sst_auth = “root:admin” binlog_format = ROW

    Events:

    ConfigMap이 클러스터에 배포가 되었고, wsrep_cluster_address 정보를 통해서 NODE_COUNT 수 만큼 올바른 항목들이 생성되어 있는 것을 확인할 수 있다.

  • KUDO 및 Operator 삭제

    $ kubectl kudo init --unsafe-self-signed-webhook-ca --upgrade --dry-run --output yaml | kubectl delete -f -

Cluster에 참여하는 Node들이 연결할 Service 구성

클러스터에 참여할 노드들을 StatefulSet에 배포할 예정이므로 이들을 연결할 방법인 서비스를 구성해야 한다.

operator.yaml 파일에 Steps, Tasks, Resources 를 아래와 같이 추가하도록 한다.

apiVersion: kudo.dev/v1beta1
appVersion: 0.1.0
kubernetesVersion: 0.16.0
kudoVersion: 0.17.2
maintainers:
- email: ccambo@gmail.com
  name: ccambo
name: galera-operator
operatorVersion: 0.1.0
plans: 
  deploy:
    strategy: serial
    phases:
      - name: deploy
        strategy: serial
        steps:
          - name: bootstrap_config
            tasks:
              - bootstrap_config
          - name: bootstrap_service
            tasks:
              - bootstrap_service
          - name: bootstrap_deploy
            tasks:
              - bootstrap_deploy
          - name: firstboot_config
            tasks:
              - firstboot_config
          - name: cluster_services    # 추가
            tasks:
              - cluster_services
tasks:
  - name: bootstrap_config
    kind: Apply
    spec:
      resources:
        - bootstrap_config.yaml
  - name: bootstrap_service
    kind: Apply
    spec:
      resources:
        - bootstrap_service.yaml
  - name: bootstrap_deploy
    kind: Apply
    spec:
      resources:
        - bootstrap_deploy.yaml
  - name: firstboot_config
    kind: Apply
    spec:
      resources:
        - galera_config.yaml
  - name: cluster_services      # 추가
    kind: Apply
    spec:
      resources:
        - hs-service.yaml

cluster_services Task에서 사용할 서비스 리소스인 templates/hs-service.yaml 파일을 생성하고 아래와 같이 구성한다.

apiVersion: v1
kind: Service
metadata:
  name: {{ .Name }}-hs
  namespace: {{ .Namespace }}
  labels:
    app: galera
    galera: {{ .Name }} 
spec:
  ports:
    - port: {{ .Params.MYSQL_PORT }}
      name: mysql
    - port: {{ .Params.SST_PORT }}
      name: sst
    - port: {{ .Params.REPLICATION_PORT }}
      name: replication
    - port: {{ .Params.IST_PORT }}
      name: ist
  clusterIP: None
  selector:
    app: galera
    instance: {{ .Name }}

ClusterIP를 가지지 않는 Headless 서비스를 생성한다. params.yaml에 정의된 Galera 구성에 필요한 모든 포트들을 설정하고 각 배포의 유일함을 보장하기 위해서 app와 instance라는 레이블을 선택하고 있다.

Headless Service인 이유는 Load balancing 주소가 필요하지 않고 StatefulSet의 각 노드에 때한 DNS 항목만 필요하기 때문이다.

지금까지 작업한 것을 기준으로 정상적으로 동작하는지 테스트를 하도록 한다.

  • 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-bootstrap-svc ClusterIP None 3306/TCP,4444/TCP,4567/TCP,4568/TCP 25s galera-operator-instance-hs ClusterIP None 3306/TCP,4444/TCP,4567/TCP,4568/TCP 9s kubernetes ClusterIP 10.96.0.1 443/TCP 28d

    $ kubectl describe service galera-operator-instance-hs

    Name: galera-operator-instance-hs 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-hs”,“namespace”:“default”,“creationTimestamp”:null,"label… kudo.dev/last-plan-execution-uid: 89dc7134-f4ae-4fee-9fbf-ecee27b5a311 kudo.dev/phase: deploy kudo.dev/plan: deploy kudo.dev/step: cluster_services Selector: app=galera,instance=galera-operator-instance Type: ClusterIP IP: None Port: mysql 3306/TCP TargetPort: 3306/TCP Endpoints: Port: sst 4444/TCP TargetPort: 4444/TCP Endpoints: Port: replication 4567/TCP TargetPort: 4567/TCP Endpoints: Port: ist 4568/TCP TargetPort: 4568/TCP Endpoints: Session Affinity: None Events:

    서비스가 클러스터에 생성되었고, 올바른 Selector를 사용하고 있고, 역시 노드가 배포되지 않았기 때문에 Endpoints가 설정되지 않았다는 것을 확인할 수 있다.

  • KUDO 및 Operator 삭제

    $ kubectl kudo init --unsafe-self-signed-webhook-ca --upgrade --dry-run --output yaml | kubectl delete -f -

Cluster에 참여할 Node 구성을 위한 StatefulSet 설정

위에서 클러스터에 참여할 노드들을 위한 ConfigMap과 Service가 정의되었기 때문에 StatefulSet을 operator.yaml에 Step과 Task로 설정한다.

apiVersion: kudo.dev/v1beta1
appVersion: 0.1.0
kubernetesVersion: 0.16.0
kudoVersion: 0.17.2
maintainers:
- email: ccambo@gmail.com
  name: ccambo
name: galera-operator
operatorVersion: 0.1.0
plans: 
  deploy:
    strategy: serial
    phases:
      - name: deploy
        strategy: serial
        steps:
          - name: bootstrap_config
            tasks:
              - bootstrap_config
          - name: bootstrap_service
            tasks:
              - bootstrap_service
          - name: bootstrap_deploy
            tasks:
              - bootstrap_deploy
          - name: firstboot_config
            tasks:
              - firstboot_config
          - name: cluster_services
            tasks:
              - cluster_services
          - name: statefulset    # 추가
            tasks:
              - statefulset
tasks:
  - name: bootstrap_config
    kind: Apply
    spec:
      resources:
        - bootstrap_config.yaml
  - name: bootstrap_service
    kind: Apply
    spec:
      resources:
        - bootstrap_service.yaml
  - name: bootstrap_deploy
    kind: Apply
    spec:
      resources:
        - bootstrap_deploy.yaml
  - name: firstboot_config
    kind: Apply
    spec:
      resources:
        - galera_config.yaml
  - name: cluster_services
    kind: Apply
    spec:
      resources:
        - hs-service.yaml
  - name: statefulset      # 추가
    kind: Apply
    spec:
      resources:
        - statefulset.yaml

주의

아래의 StatefulSet 설정은 실제 mariadb가 설치되어 동작하는 노드 컨테이너를 생성하는 것이기 때문에 Persistent Volume 구성이 필요하지만 샘플에서는 Kubernetes에 기본 생성되어 있는 Default Storage Class를 이용하는 것으로 되어 있고 Persistent Volume에 대한 언급이 없다.

Kubernetes Cluster를 구성하고 나면 (Test는 Openstack 베이스로 구성) Default Storage가 존재하지 않고, Galera Cluster에 여러 개의 Node들이 참여를 하고 각기 Persitentn Volume을 사용해야 하기 때문에 별도로 NFS (Network File System) 서버를 CentOS 8에 설치하고 Kubernetes에 Dynamic NFS Client Provisioner와 Storage Class를 구성한 것으로 적용했다.

참고 문서

이제 statefulset Task에서 사용할 리소스인 templates/statefulset.yaml 파일을 생성하고 아래와 같이 구성한다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Name }}-{{ .OperatorName }}
  namespace: {{ .Namespace }}
  labels:
    galera: {{ .OperatorName }}
    app: galera
    instance: {{ .Name }}
  annotations:
    reloader.kudo.dev/auto: "true"
spec:
  selector:
    matchLabels:
      app: galera
      galera: {{ .OperatorName }}
      instance: {{ .Name }}
  serviceName: {{ .Name }}-hs
  replicas: {{ .Params.NODE_COUNT }}
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: galera
        galera: {{ .OperatorName }}
        instance: {{ .Name }}
    spec:
      initContainers:
      # Stop the image bootstrap script from trying to set up single master
      - name: {{ .Name }}-init
        image: busybox:1.28
        command: ['sh', '-c', 'set -x; if [ ! -d /datadir/mysql ]; then mkdir /datadir/mysql; fi']
        volumeMounts:
          - name: {{ .Name }}-datadir
            mountPath: "/datadir"
      containers:
      - name: mariadb
        image: mariadb:latest
        args:
        - "--ignore_db_dirs=lost+found"
        env:
          # Use secret in real usage
        - name: MYSQL_ROOT_PASSWORD
          value: {{ .Params.MYSQL_ROOT_PASSWORD }}
        ports:
        - containerPort: {{ .Params.MYSQL_PORT }}
          name: mysql
        - containerPort: {{ .Params.SST_PORT }}
          name: sst
        - containerPort: {{ .Params.REPLICATION_PORT }}
          name: replication
        - containerPort: {{ .Params.IST_PORT }}
          name: ist
        livenessProbe:
          exec:
            command: ["mysqladmin", "-p{{ .Params.MYSQL_ROOT_PASSWORD }}", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            # Check we can execute queries over TCP (skip-networking is off).
            command: ["mysql", "-p{{ .Params.MYSQL_ROOT_PASSWORD }}", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
        volumeMounts:
        - name: {{ .Name }}-config
          mountPath: /etc/mysql/conf.d
        - name: {{ .Name }}-datadir
          mountPath: /var/lib/mysql
      volumes:
      - name: {{ .Name }}-config
        configMap:
          name: {{ .Name }}-nodeconfig
          items:
            - key: galera.cnf
              path: galera.cnf
            - key: innodb.cnf
              path: innodb.cnf
  volumeClaimTemplates:
    - metadata:
        name: {{ .Name }}-datadir
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: {{ .Params.VOLUME_SIZE }}

위의 StatefulSet 구성에는 몇 가지 주의해야할 부분들이 존재한다.

mySQL 데이터 디렉터리를 /var/lib/mysql 경로로 Persistent Volumes를 사용하고, /etc/mysql/conf.d에 configMap의 key/path 정보를 이용해서 설정 파일들을 시작할 때 사용할 수 있도록 마운트한다.

  volumeMounts:
  - name: {{ .Name }}-config
    mountPath: /etc/mysql/conf.d
  - name: {{ .Name }}-datadir
    mountPath: /var/lib/mysql
volumes:
- name: {{ .Name }}-config
  configMap:
    name: {{ .Name }}-nodeconfig
    items:
      - key: galera.cnf
        path: galera.cnf
      - key: innodb.cnf
        path: innodb.cnf

StatefulSet이므로 각 Replica들에 필요한 VolumeClaims들을 추가하는 VolumeClaimTemplate를 정의한다.

volumeClaimTemplates:
  - metadata:
      name: {{ .Name }}-datadir
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: {{ .Params.VOLUME_SIZE }}

MariaDB 컨테이너 이미지는 컨테이너가 처음 시작될 때 단일 노드 인스턴스를 설정하기 위한 부트스트랩 스크립트를 가지고 있다. 샘플에서는 이를 재 정의해서 /var/lib/mysql 경로를 확인해서 데이터베이스가 이미 구성되었다면 부트스트랩 과정을 생략하도록 한다.

init container를 이용해서 데이터 디렉터리를 마운트하고 스크립트가 실행되기 전에 디렉터리를 생성해서 ConfigMap에서 생성한 설정에 따라서 Galera 프로세스가 올바르게 실행되고, 클러스터에 동기화될 수 있도록 재 정의한다.

initContainers:
# Stop the image bootstrap script from trying to set up single master
- name: {{ .Name }}-init
  image: busybox:1.28
  command: ['sh', '-c', 'set -x; if [ ! -d /datadir/mysql ]; then mkdir /datadir/mysql; fi']
  volumeMounts:
    - name: {{ .Name }}-datadir
      mountPath: "/datadir"

NODE_COUNT 파라미터를 이용해서 StatefulSet내에 포함되어야할 Replica의 수를 설정했다.

replicas: {{ .Params.NODE_COUNT }}

PART 1 에서 정의했던 포트 관련 파라미터들을 사용해서 노드들의 mySQL에서 사용할 포트들을 정의했다.

ports:
  - containerPort: {{ .Params.MYSQL_PORT }}
    name: mysql
  - containerPort: {{ .Params.SST_PORT }}
    name: sst
  - containerPort: {{ .Params.REPLICATION_PORT }}
    name: replication
  - containerPort: {{ .Params.IST_PORT }}
    name: ist

새롭게 사용된 VOLUME_SIZE 라는 파라미터를 params.yaml에 추가하도록 한다.

apiVersion: kudo.dev/v1beta1
parameters:
  - name: SST_USER
    description: "User to perform SST as"
    default: "root"
  - name: SST_PASSWORD
    description: "Password for SST user"
    default: "admin"
  - name: MYSQL_PORT
    description: "MySQL port"
    default: "3306"
  - name: SST_PORT
    description: "SST port"
    default: "4444"
  - name: REPLICATION_PORT
    description: "Replication port"
    default: "4567"
  - name: IST_PORT
    description: "IST port"
    default: "4568"
  - name: MYSQL_ROOT_PASSWORD
    description: "MySQL root password"
    default: "admin"
  - name: NODE_COUNT
    description: "Number of nodes to create in the cluster"
    default: "3"
  - name: VOLUME_SIZE     # 추가
    description: "Size of persistent volume"
    default: "10M"

테스트를 위한 것이기 때문에 아주 작은 기본 크기를 추가했다.

지금까지 작업한 것을 기준으로 정상적으로 동작하는지 테스트를 하도록 한다. 테스트를 위한 것이기 때문에 params.yaml 파일에 정의된 NODE_COUNT 파라미터의 기본 값을 1로 변경하고 진행해도 상관없다. StatefulSet은 실제 노드 및 MariaDB도 구성하므로 시간이 오래 걸릴 수 있다.

  • 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
  • 설치된 Pod 확인

    $ kubectl get pods

    실행 중인 클러스터에 모든 인스턴스들이 생성된 것을 확인할 수 있다. 하나의 파드를 선택해서 로그 정보를 통해서 Galera가 정상적으로 동작하고 있는지를 확인한다.

    $ kubectl logs galera-operator-instance-galera-operator-0
  • KUDO 및 Operator 삭제

    $ kubectl kudo init --unsafe-self-signed-webhook-ca --upgrade --dry-run --output yaml | kubectl delete -f -

Conclusion

지금까지의 작업으로 StatefulSet이 올바르게 설정되었고, 모든 노드가 Galera Cluster에 참여하고 있다는 것을 확인할 수 있다.

PART 3 에서는 이제 필요하지 않은 부트스트랩 노드를 제거하고, 클러스터를 외부에서 연결할 수 있도록 설정하고, 운영되는 동안 안정적으로 Scale Up/Down 할 수 있도록 기능을 추가할 것이다.

참고 자료

댓글

이 블로그의 인기 게시물

OData 에 대해서 알아보자.

얼마 전에 어떤 회사에 인터뷰를 하러 간 적이 있었다. 당시 그 회사는 자체 솔루션을 개발할 기술인력을 찾고 있었고 내부적으로 OData를 사용한다고 했다. 좀 창피한 이야기일 수도 있지만 나름 기술적인 부분에서는 많은 정보를 가지고 있다고 했던 것이 무색하게 OData란 단어를 그 회사 사장님에게서 처음 들었다. 작고, 단순한 사이트들만을 계속해서 작업을 하다 보니 어느덧 큰 줄기들을 잃어버린 것을 느끼기 시작했다. 명색이 개발이 좋고, 기술적인 기반을 만들려고 하는 인간이 단어조차도 모른다는 것은 있을 수 없는 것이라서 다시 새로운 단어들과 개념들을 알아보는 시간을 가지려고 한다. OData (Open Data Protocol) 란? 간단히 정리하면 웹 상에서 손쉽게 데이터를 조회하거나 수정할 수 있도록 주고 받는 웹(프로토콜)을 말한다. 서비스 제공자 입장에서는 웹으로 데이터를 제공하는 방식으로 각 포탈 사이트들이 제공하는 OPEN API 포맷을 독자적인 형식이 아니라 오픈된 공통규약으로 제공 가능하며, 개발자는 이 정보를 다양한 언어의 클라이언트 라이브러리로 어플리케이션에서 소비할 수 있도록 사용하면 된다. 공식 사이트는 www.odata.org 이며 많은 언어들을 지원하고 있다. 좀더 상세하게 정의를 해 보면 OData는 Atom Publishing Protocol  (RFC4287) 의 확장 형식이고 REST (REpresentational State Transfer) Protocol 이다. 따라서 웹 브라우저에서 OData 서비스로 노출된 데이터를 볼 수 있다. 그리고 AtomPub 의 확장이라고 했듯이 데이터의 조회만으로 한정되는 것이 아니라 CRUD 작업이 모두 가능하다. Example 웹 브라우저에서 http://services.odata.org/website/odata.svc 를 열어 보도록 하자. This XML file does not appear to have any style in...

C# 에서 Timer 사용할 때 주의할 점.

예전에 알고 지내시던 분의 질문을 받았다. Windows Forms 개발을 하는데, 주기적 (대략 1분)으로 데이터 요청을 하는 프로그램을 작성하기 위해서 Timer 를 사용하는데, 어떤 기능을 처리해야 하기 때문에 Sleep 을 같이 사용했다고 한다. 여기서 발생하는 문제는 Sleep 5초를 주었더니, Timer 까지 5초 동안 멈춘다는 것이다. Timer 라는 것은 기본적으로 시간의 흐름을 측정하는 기능이기 때문에 Sleep 을 했다고 해서 Timer 가 멈추는 일은 생겨서는 안된다. 그러나 실제 샘플을 만들어 보면 ... Timer 가 Sleep 만큼 동작이 멈추는 것을 확인할 수 있다. Windows Forms 는 UI Thread 를 사용하는 것으로 최적화 되어 있으며 여기서 Timer 를 쓰면 UI Thread 에 최적화된 System.Windows.Forms.Timer 가 사용된다. 여기서 문제의 발생이 시작되는 것이다. Sleep 을 사용하게 되면 UI Thread 가 Sleep 이 걸리기 때문에 여기에 속한 Timer 까지도 멈추는 것이다. 이런 문제를 해결하기 위해서는 System.Threading.Timer 를 사용해야 한다. 이 Timer 는 별도의 Thread 에서 동작하기 때문에 Sleep 의 영향을 받지 않는다. 언뜻 보면 쉬운 해결 방법인 것 같지만 Thread 가 분리되었기 때문에 Timer 가 돌아가는 Thread 에서 UI Thread 의 메서드나 컨트롤에 접근하기 위해서는 별도의 명령을 사용해야 하는 문제가 존재한다. 자~ 그럼 여기서 Timer 에 대해서 다시 한번 정리해 보도록 하자. .NET 에서 제공하는 Timer 들 .NET 에서는 기본적으로 3가지 Timer를 제공하고 있다. (MSDN) System.Windows.Forms.Timer - 사용자가 지정한 간격마다 이벤트를 발생시키며 Windows Forms 응용 프로그램에서 사용할 수 있도록 최적화 되어 있다. System...

[Logging] NLog 사용법 정리...

SCSF 에는 기본적으로 Enterprise Library가 사용된다. 예전에도 그랬지만 기능은 훌륭하고 많은 부분에서 최적화(?)된 것일지도 모르지만, 역시나 사용하기에는 뭔가 모르게 무겁고, 사용하지 않는 기능이 더 많다라는 느낌을 지울수가 없다. 이번 프로젝트도 SCSF를 기반으로 하고 있지만, Enterprise Library를 걷어내고 각 부분에 전문화된 오픈 소스를 사용하기로 하였다. 예전에는 Log4Net을 사용했지만, 대량 사용자 환경에서는 메모리 누수와 기타 문제점이 존재한다는 MS 컨설턴트(?)의 전해진 말을 들은 후로는 사용하지 않는다. 대안으로 사용하는 것이 NLog 이다. 조금 후에는 3.0 버전도 나온다고 홈 페이지에 기재되어 있지만, 그 때가 되면 프로젝트는 끝나기 때문에 현재 2.1.0 버전을 사용하기로 했다. [원본 출처] http://cloverink.net/most-useful-nlog-configurations-closed/ 위의 참조 자료에는 다양한 정보들이 존재하므로 꼭 링크를 통해서 관련된 정보를 확인하고 이해하는 것이 좋을 듯 하다. 여기서는 당장 필요한 부분만을 정리하도록 한다. [ Logger 찾기 ] 기본적으로 Logger가 존재하는 클래스를 기반으로 Logger 정보를 구성한다. Logger logger = LogManager.GetCurrentClassLogger(); 주로 Namespace 기반으로 Logger를 설정하는 경우에 유연하게 사용할 수 있다. 또 다른 방법으로는 지정한 문자열로 특정 Logger를 직접 선택하는 방법도 제공된다. 이를 혼용해서 Namespace와 직접 지정 방식을 같이 사용할 수도 있다. 물론 Logger 환경 설정에서 Wildcard (*)를 지정할 수도 있다. Logger logger = LogManager.GetLogger("Database.Connect"); Logger logger = LogManager.Get...