헬름 고급 명령어(Advanced Commands)

이민석's avatar
Apr 30, 2024
헬름 고급 명령어(Advanced Commands)

헬름 + 쿠버네티스 가이드북은 Bharath Thippireddy의 Helm Kubernetes Packaging Manager for Developers and DevOps을 보고 작성되었습니다.

가이드북의 전체 목차 및 인덱싱은 헬름 + 쿠버네티스 가이드북 문서를 참고해주세요.

사전 지식

  1. 필수 프로그램 설치 및 환경 구성

필수 프로그램 설치 및 환경 구성

Unchaptered (Blog) | 헬름 작동 과정(Helm in Action)에서 언급한 기본적인 Helm, Kubernetes-CLI(KUBECONFIG) 등이 완료된 상태로 진행해주세요.

헬름 고급 명령어

  1. 헬름 릴리즈 워크플로(Helm Release Worflow)

헬름 릴리즈 워크플로(Helm Release Workflow)

helm install mydb bitnami/mysql 명령어를 입력했을 때, 일어나는 일

  1. 대상 차트와 그 차트의 종속성을 불러오기

  2. 동적 변수 파일(values.yaml)을 파싱하기

  3. 두 파일을 기반으로 배포할 얌 파일 세트(YAMLs) 생성하기

  4. 차트(YAMLs)를 쿠버네티스 오브젝트에 파싱하고 유효성 검사 진행

  5. 차트(YAMLs)를 생성하고 쿠버네티스에 전송
     

헬름 드라이 런(helm ~ --dry-run)

helm install mydb bitnami/mysql --dry-run 명령어를 입력했을 때, 일어나는 일

헬름 명령어에 --dry-run 명령어를 입력하면 헬름 릴리즈 워크플로가 1~4 단게만 진행이 됩니다.

즉, 대상 헬름 명령어를 실행햇을 때의 출력값(Outputs)을 받게 됩니다.

Unchaptered (Blog) | 헬름 작동 과정(Helm in Action)에서 진행한 실습 명령어출력값을 기반으로 분석을 해보았습니다.

출력값은 크게 3가지로 구성되어 있습니다.

  • 릴리즈 정보

  • 쿠버네티스 탬플릿(=선언 파일들)

  • 릴리즈 노트

  • 실습 멸영어

    helm install mydb bitnami/mysql --values values.yaml --dry-run
  • 출력값

    출력값은 릴리즈 정보 / 쿠버네티스 선언 파일 / 릴리즈 노트로 구성됩니다.

    NAME: mydb
    LAST DEPLOYED: Tue Apr 30 13:04:12 2024
    NAMESPACE: default
    STATUS: pending-install
    REVISION: 1
    TEST SUITE: None
    HOOKS:
    MANIFEST:
    ---
    # Source: mysql/templates/networkpolicy.yaml
    kind: NetworkPolicy
    apiVersion: networking.k8s.io/v1
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
    spec:
      podSelector:
        matchLabels:
          app.kubernetes.io/instance: mydb
          app.kubernetes.io/managed-by: Helm
          app.kubernetes.io/name: mysql
          app.kubernetes.io/version: 8.0.36
          helm.sh/chart: mysql-10.1.1
      policyTypes:
        - Ingress
        - Egress
      egress:
        - {}
      ingress:
        - ports:
            - port: 3306
            - port: 3306
    ---
    # Source: mysql/templates/serviceaccount.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
    automountServiceAccountToken: false
    secrets:
      - name: mydb-mysql
    ---
    # Source: mysql/templates/secrets.yaml
    apiVersion: v1
    kind: Secret
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
    type: Opaque
    data:
      mysql-root-password: "dGVzdDEyMzQ1NjY2Njc3Nw=="
      mysql-password: "NGhCVnZOTEM2NQ=="
    ---
    # Source: mysql/templates/primary/configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
        app.kubernetes.io/component: primary
    data:
      my.cnf: |-
        [mysqld]
        default_authentication_plugin=mysql_native_password
        skip-name-resolve
        explicit_defaults_for_timestamp
        basedir=/opt/bitnami/mysql
        plugin_dir=/opt/bitnami/mysql/lib/plugin
        port= 3306
        socket=/opt/bitnami/mysql/tmp/mysql.sock
        datadir=/bitnami/mysql/data
        tmpdir=/opt/bitnami/mysql/tmp
        max_allowed_packet=16M
        bind-address=*
        pid-file=/opt/bitnami/mysql/tmp/mysqld.pid
        log-error=/opt/bitnami/mysql/logs/mysqld.log
        character-set-server=UTF8
        slow_query_log=0
        long_query_time=10.0
    
        [client]
        port=3306
        socket=/opt/bitnami/mysql/tmp/mysql.sock
        default-character-set=UTF8
        plugin_dir=/opt/bitnami/mysql/lib/plugin
    
        [manager]
        port=3306
        socket=/opt/bitnami/mysql/tmp/mysql.sock
        pid-file=/opt/bitnami/mysql/tmp/mysqld.pid
    ---
    # Source: mysql/templates/primary/svc-headless.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: mydb-mysql-headless
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
        app.kubernetes.io/component: primary
    spec:
      type: ClusterIP
      clusterIP: None
      publishNotReadyAddresses: true
      ports:
        - name: mysql
          port: 3306
          targetPort: mysql
      selector:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/name: mysql
        app.kubernetes.io/component: primary
    ---
    # Source: mysql/templates/primary/svc.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
        app.kubernetes.io/component: primary
    spec:
      type: ClusterIP
      sessionAffinity: None
      ports:
        - name: mysql
          port: 3306
          protocol: TCP
          targetPort: mysql
          nodePort: null
      selector:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/name: mysql
        app.kubernetes.io/component: primary
    ---
    # Source: mysql/templates/primary/statefulset.yaml
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
        app.kubernetes.io/component: primary
    spec:
      replicas: 1
      podManagementPolicy: ""
      selector:
        matchLabels:
          app.kubernetes.io/instance: mydb
          app.kubernetes.io/name: mysql
          app.kubernetes.io/component: primary
      serviceName: mydb-mysql
      updateStrategy:
        type: RollingUpdate
      template:
        metadata:
          annotations:
            checksum/configuration: dddb2b4e5f47831da6614b7152d3eabe2232e9f2eb587c9cada54a7d034bc30b
          labels:
            app.kubernetes.io/instance: mydb
            app.kubernetes.io/managed-by: Helm
            app.kubernetes.io/name: mysql
            app.kubernetes.io/version: 8.0.36
            helm.sh/chart: mysql-10.1.1
            app.kubernetes.io/component: primary
        spec:
          serviceAccountName: mydb-mysql
    
          automountServiceAccountToken: false
          affinity:
            podAffinity:
    
            podAntiAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
                - podAffinityTerm:
                    labelSelector:
                      matchLabels:
                        app.kubernetes.io/instance: mydb
                        app.kubernetes.io/name: mysql
                    topologyKey: kubernetes.io/hostname
                  weight: 1
            nodeAffinity:
    
          securityContext:
            fsGroup: 1001
            fsGroupChangePolicy: Always
            supplementalGroups: []
            sysctls: []
          initContainers:
            - name: preserve-logs-symlinks
              image: docker.io/bitnami/mysql:8.0.36-debian-12-r10
              imagePullPolicy: "IfNotPresent"
              securityContext:
                allowPrivilegeEscalation: false
                capabilities:
                  drop:
                  - ALL
                readOnlyRootFilesystem: true
                runAsGroup: 1001
                runAsNonRoot: true
                runAsUser: 1001
                seLinuxOptions: {}
                seccompProfile:
                  type: RuntimeDefault
              resources:
                limits:
                  cpu: 750m
                  ephemeral-storage: 1024Mi
                  memory: 768Mi
                requests:
                  cpu: 500m
                  ephemeral-storage: 50Mi
                  memory: 512Mi
              command:
                - /bin/bash
              args:
                - -ec
                - |
                  #!/bin/bash
    
                  . /opt/bitnami/scripts/libfs.sh
                  # We copy the logs folder because it has symlinks to stdout and stderr
                  if ! is_dir_empty /opt/bitnami/mysql/logs; then
                    cp -r /opt/bitnami/mysql/logs /emptydir/app-logs-dir
                  fi
              volumeMounts:
                - name: empty-dir
                  mountPath: /emptydir
          containers:
            - name: mysql
              image: docker.io/bitnami/mysql:8.0.36-debian-12-r10
              imagePullPolicy: "IfNotPresent"
              securityContext:
                allowPrivilegeEscalation: false
                capabilities:
                  drop:
                  - ALL
                readOnlyRootFilesystem: true
                runAsGroup: 1001
                runAsNonRoot: true
                runAsUser: 1001
                seLinuxOptions: {}
                seccompProfile:
                  type: RuntimeDefault
              env:
                - name: BITNAMI_DEBUG
                  value: "false"
                - name: MYSQL_ROOT_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: mydb-mysql
                      key: mysql-root-password
                - name: MYSQL_PORT
                  value: "3306"
                - name: MYSQL_DATABASE
                  value: "my_database"
              envFrom:
              ports:
                - name: mysql
                  containerPort: 3306
              livenessProbe:
                failureThreshold: 3
                initialDelaySeconds: 5
                periodSeconds: 10
                successThreshold: 1
                timeoutSeconds: 1
                exec:
                  command:
                    - /bin/bash
                    - -ec
                    - |
                      password_aux="${MYSQL_ROOT_PASSWORD:-}"
                      if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
                          password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
                      fi
                      mysqladmin status -uroot -p"${password_aux}"
              readinessProbe:
                failureThreshold: 3
                initialDelaySeconds: 5
                periodSeconds: 10
                successThreshold: 1
                timeoutSeconds: 1
                exec:
                  command:
                    - /bin/bash
                    - -ec
                    - |
                      password_aux="${MYSQL_ROOT_PASSWORD:-}"
                      if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
                          password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
                      fi
                      mysqladmin status -uroot -p"${password_aux}"
              startupProbe:
                failureThreshold: 10
                initialDelaySeconds: 15
                periodSeconds: 10
                successThreshold: 1
                timeoutSeconds: 1
                exec:
                  command:
                    - /bin/bash
                    - -ec
                    - |
                      password_aux="${MYSQL_ROOT_PASSWORD:-}"
                      if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
                          password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
                      fi
                      mysqladmin status -uroot -p"${password_aux}"
              resources:
                limits:
                  cpu: 750m
                  ephemeral-storage: 1024Mi
                  memory: 768Mi
                requests:
                  cpu: 500m
                  ephemeral-storage: 50Mi
                  memory: 512Mi
              volumeMounts:
                - name: data
                  mountPath: /bitnami/mysql
                - name: empty-dir
                  mountPath: /tmp
                  subPath: tmp-dir
                - name: empty-dir
                  mountPath: /opt/bitnami/mysql/conf
                  subPath: app-conf-dir
                - name: empty-dir
                  mountPath: /opt/bitnami/mysql/tmp
                  subPath: app-tmp-dir
                - name: empty-dir
                  mountPath: /opt/bitnami/mysql/logs
                  subPath: app-logs-dir
                - name: config
                  mountPath: /opt/bitnami/mysql/conf/my.cnf
                  subPath: my.cnf
          volumes:
            - name: config
              configMap:
                name: mydb-mysql
            - name: empty-dir
              emptyDir: {}
      volumeClaimTemplates:
        - metadata:
            name: data
            labels:
              app.kubernetes.io/instance: mydb
              app.kubernetes.io/name: mysql
              app.kubernetes.io/component: primary
          spec:
            accessModes:
              - "ReadWriteOnce"
            resources:
              requests:
                storage: "8Gi"
    
    NOTES:
    CHART NAME: mysql
    CHART VERSION: 10.1.1
    APP VERSION: 8.0.36
    
    ** Please be patient while the chart is being deployed **
    
    Tip:
    
      Watch the deployment status using the command: kubectl get pods -w --namespace default
    
    Services:
    
      echo Primary: mydb-mysql.default.svc.cluster.local:3306
    
    Execute the following to get the administrator credentials:
    
      echo Username: root
      MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace default mydb-mysql -o jsonpath="{.data.mysql-root-password}" | base64 -d)
    
    To connect to your database:
    
      1. Run a pod that you can use as a client:
    
          kubectl run mydb-mysql-client --rm --tty -i --restart='Never' --image  docker.io/bitnami/mysql:8.0.36-debian-12-r10 --namespace default --env MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD --command -- bash
    
      2. To connect to primary service (read/write):
    
          mysql -h mydb-mysql.default.svc.cluster.local -uroot -p"$MYSQL_ROOT_PASSWORD"
    
    
    
    
    
    
    WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs:
      - primary.resources
      - secondary.resources
    +info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/

헬름 탬플릿(helm template ~)

헬름 드라이 런(helm --dry-run) 명령어가 생기고 나서, 다른 목적으로 사용하는 사례가 생겨났습니다. 가장 대표적으로 헬름 드라이 런의 출력값을 쿠버네티스 탬플릿으로 쓰는 것이었습니다.

이 방식이 가지고 있는 2가지 문제점을 알고 이를 극복해봅시다.

  1. 유효하지 않은 YAML 파일 형식의 존재

    helm install mydb bitnami/mysql --values values.yaml --dry-run 명령어의 출력값은 YAML 형식과 100% 호환되지 않습니다.

  2. 쿠버네티스 클러스터가 반드시 필요함

    helm upgrade mydb bitnami/mysql --values values.yaml --dry-run 명령어는 install 이후에만 실행할 수 있습니다. 따라서 테스트를 진행하려면 배포가 완료되어 있어야 하며, Kubernetes Cluster와 항상 연결이 되어 있어야 합니다.

따라서 이런 문제를 해결하기 위해서 helm template <LOCAL> <HOST>/<TARGET> 명령어가 나왔습니다. 헬름 탬플릿의 주요 특징은 다음과 같습니다.

  1. [장점] 항상 유효한 YAML 파일 반환

  2. [장점] 쿠버네티스 클러스터가 필요 없음

  3. [단점] 유효성 검사를 진행하지 않음

    헬름 드라이런(--dry-run)은 4단계에서 탬플릿을 유효성 검사합니다. 이 과정에서 쿠버네티스 클러스터가 필요합니다.
    하지만 헬름 탬플릿(template)은 이런 과정을 따르지 않으므로 유효성 검사도 하지 않고 쿠버네티스 클러스터도 필요 없습니다.

  • 명령어

    helm template mydb bitnami/mysql --values values.yaml
  • 출력값

    ---
    # Source: mysql/templates/networkpolicy.yaml
    kind: NetworkPolicy
    apiVersion: networking.k8s.io/v1
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
    spec:
      podSelector:
        matchLabels:
          app.kubernetes.io/instance: mydb
          app.kubernetes.io/managed-by: Helm
          app.kubernetes.io/name: mysql
          app.kubernetes.io/version: 8.0.36
          helm.sh/chart: mysql-10.1.1
      policyTypes:
        - Ingress
        - Egress
      egress:
        - {}
      ingress:
        - ports:
            - port: 3306
            - port: 3306
    ---
    # Source: mysql/templates/serviceaccount.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
    automountServiceAccountToken: false
    secrets:
      - name: mydb-mysql
    ---
    # Source: mysql/templates/secrets.yaml
    apiVersion: v1
    kind: Secret
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
    type: Opaque
    data:
      mysql-root-password: "dGVzdDEyMzQ1NjY2Njc3Nw=="
      mysql-password: "anROYmFaT0JnRQ=="
    ---
    # Source: mysql/templates/primary/configmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
        app.kubernetes.io/component: primary
    data:
      my.cnf: |-
        [mysqld]
        default_authentication_plugin=mysql_native_password
        skip-name-resolve
        explicit_defaults_for_timestamp
        basedir=/opt/bitnami/mysql
        plugin_dir=/opt/bitnami/mysql/lib/plugin
        port= 3306
        socket=/opt/bitnami/mysql/tmp/mysql.sock
        datadir=/bitnami/mysql/data
        tmpdir=/opt/bitnami/mysql/tmp
        max_allowed_packet=16M
        bind-address=*
        pid-file=/opt/bitnami/mysql/tmp/mysqld.pid
        log-error=/opt/bitnami/mysql/logs/mysqld.log
        character-set-server=UTF8
        slow_query_log=0
        long_query_time=10.0
    
        [client]
        port=3306
        socket=/opt/bitnami/mysql/tmp/mysql.sock
        default-character-set=UTF8
        plugin_dir=/opt/bitnami/mysql/lib/plugin
    
        [manager]
        port=3306
        socket=/opt/bitnami/mysql/tmp/mysql.sock
        pid-file=/opt/bitnami/mysql/tmp/mysqld.pid
    ---
    # Source: mysql/templates/primary/svc-headless.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: mydb-mysql-headless
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
        app.kubernetes.io/component: primary
    spec:
      type: ClusterIP
      clusterIP: None
      publishNotReadyAddresses: true
      ports:
        - name: mysql
          port: 3306
          targetPort: mysql
      selector:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/name: mysql
        app.kubernetes.io/component: primary
    ---
    # Source: mysql/templates/primary/svc.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
        app.kubernetes.io/component: primary
    spec:
      type: ClusterIP
      sessionAffinity: None
      ports:
        - name: mysql
          port: 3306
          protocol: TCP
          targetPort: mysql
          nodePort: null
      selector:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/name: mysql
        app.kubernetes.io/component: primary
    ---
    # Source: mysql/templates/primary/statefulset.yaml
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: mydb-mysql
      namespace: "default"
      labels:
        app.kubernetes.io/instance: mydb
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: mysql
        app.kubernetes.io/version: 8.0.36
        helm.sh/chart: mysql-10.1.1
        app.kubernetes.io/component: primary
    spec:
      replicas: 1
      podManagementPolicy: ""
      selector:
        matchLabels:
          app.kubernetes.io/instance: mydb
          app.kubernetes.io/name: mysql
          app.kubernetes.io/component: primary
      serviceName: mydb-mysql
      updateStrategy:
        type: RollingUpdate
      template:
        metadata:
          annotations:
            checksum/configuration: dddb2b4e5f47831da6614b7152d3eabe2232e9f2eb587c9cada54a7d034bc30b
          labels:
            app.kubernetes.io/instance: mydb
            app.kubernetes.io/managed-by: Helm
            app.kubernetes.io/name: mysql
            app.kubernetes.io/version: 8.0.36
            helm.sh/chart: mysql-10.1.1
            app.kubernetes.io/component: primary
        spec:
          serviceAccountName: mydb-mysql
    
          automountServiceAccountToken: false
          affinity:
            podAffinity:
    
            podAntiAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
                - podAffinityTerm:
                    labelSelector:
                      matchLabels:
                        app.kubernetes.io/instance: mydb
                        app.kubernetes.io/name: mysql
                    topologyKey: kubernetes.io/hostname
                  weight: 1
            nodeAffinity:
    
          securityContext:
            fsGroup: 1001
            fsGroupChangePolicy: Always
            supplementalGroups: []
            sysctls: []
          initContainers:
            - name: preserve-logs-symlinks
              image: docker.io/bitnami/mysql:8.0.36-debian-12-r10
              imagePullPolicy: "IfNotPresent"
              securityContext:
                allowPrivilegeEscalation: false
                capabilities:
                  drop:
                  - ALL
                readOnlyRootFilesystem: true
                runAsGroup: 1001
                runAsNonRoot: true
                runAsUser: 1001
                seLinuxOptions: {}
                seccompProfile:
                  type: RuntimeDefault
              resources:
                limits:
                  cpu: 750m
                  ephemeral-storage: 1024Mi
                  memory: 768Mi
                requests:
                  cpu: 500m
                  ephemeral-storage: 50Mi
                  memory: 512Mi
              command:
                - /bin/bash
              args:
                - -ec
                - |
                  #!/bin/bash
    
                  . /opt/bitnami/scripts/libfs.sh
                  # We copy the logs folder because it has symlinks to stdout and stderr
                  if ! is_dir_empty /opt/bitnami/mysql/logs; then
                    cp -r /opt/bitnami/mysql/logs /emptydir/app-logs-dir
                  fi
              volumeMounts:
                - name: empty-dir
                  mountPath: /emptydir
          containers:
            - name: mysql
              image: docker.io/bitnami/mysql:8.0.36-debian-12-r10
              imagePullPolicy: "IfNotPresent"
              securityContext:
                allowPrivilegeEscalation: false
                capabilities:
                  drop:
                  - ALL
                readOnlyRootFilesystem: true
                runAsGroup: 1001
                runAsNonRoot: true
                runAsUser: 1001
                seLinuxOptions: {}
                seccompProfile:
                  type: RuntimeDefault
              env:
                - name: BITNAMI_DEBUG
                  value: "false"
                - name: MYSQL_ROOT_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: mydb-mysql
                      key: mysql-root-password
                - name: MYSQL_PORT
                  value: "3306"
                - name: MYSQL_DATABASE
                  value: "my_database"
              envFrom:
              ports:
                - name: mysql
                  containerPort: 3306
              livenessProbe:
                failureThreshold: 3
                initialDelaySeconds: 5
                periodSeconds: 10
                successThreshold: 1
                timeoutSeconds: 1
                exec:
                  command:
                    - /bin/bash
                    - -ec
                    - |
                      password_aux="${MYSQL_ROOT_PASSWORD:-}"
                      if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
                          password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
                      fi
                      mysqladmin status -uroot -p"${password_aux}"
              readinessProbe:
                failureThreshold: 3
                initialDelaySeconds: 5
                periodSeconds: 10
                successThreshold: 1
                timeoutSeconds: 1
                exec:
                  command:
                    - /bin/bash
                    - -ec
                    - |
                      password_aux="${MYSQL_ROOT_PASSWORD:-}"
                      if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
                          password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
                      fi
                      mysqladmin status -uroot -p"${password_aux}"
              startupProbe:
                failureThreshold: 10
                initialDelaySeconds: 15
                periodSeconds: 10
                successThreshold: 1
                timeoutSeconds: 1
                exec:
                  command:
                    - /bin/bash
                    - -ec
                    - |
                      password_aux="${MYSQL_ROOT_PASSWORD:-}"
                      if [[ -f "${MYSQL_ROOT_PASSWORD_FILE:-}" ]]; then
                          password_aux=$(cat "$MYSQL_ROOT_PASSWORD_FILE")
                      fi
                      mysqladmin status -uroot -p"${password_aux}"
              resources:
                limits:
                  cpu: 750m
                  ephemeral-storage: 1024Mi
                  memory: 768Mi
                requests:
                  cpu: 500m
                  ephemeral-storage: 50Mi
                  memory: 512Mi
              volumeMounts:
                - name: data
                  mountPath: /bitnami/mysql
                - name: empty-dir
                  mountPath: /tmp
                  subPath: tmp-dir
                - name: empty-dir
                  mountPath: /opt/bitnami/mysql/conf
                  subPath: app-conf-dir
                - name: empty-dir
                  mountPath: /opt/bitnami/mysql/tmp
                  subPath: app-tmp-dir
                - name: empty-dir
                  mountPath: /opt/bitnami/mysql/logs
                  subPath: app-logs-dir
                - name: config
                  mountPath: /opt/bitnami/mysql/conf/my.cnf
                  subPath: my.cnf
          volumes:
            - name: config
              configMap:
                name: mydb-mysql
            - name: empty-dir
              emptyDir: {}
      volumeClaimTemplates:
        - metadata:
            name: data
            labels:
              app.kubernetes.io/instance: mydb
              app.kubernetes.io/name: mysql
              app.kubernetes.io/component: primary
          spec:
            accessModes:
              - "ReadWriteOnce"
            resources:
              requests:
                storage: "8Gi"

헬름 릴리즈 레코드(helm release record)

  1. 헬름 설치하기

    helm install mydb bitnami/mysql --valeus values.yaml
  2. 헬름 설치 조회하기

    helm ls
  3. 쿠버네티스 시크릿 조회하기

    kubectl get secrets

3단계 명령어를 실행하고 나면 아래 출력값이 나옵니다.

NAME                         TYPE                 DATA   AGE
mydb-mysql                   Opaque               2      6s
sh.helm.release.v1.mydb.v1   helm.sh/release.v1   1      6s

여기서 mydb-mysql 파일 Unchaptered (Blog) | 헬름 작동 과정(Helm in Action) # 헬름 동적 변수 조회하기에서 다뤘습니다.

하지만 sh.helm.release.v1.mydb.v1는 그 용도를 언급하지 않았습니다. 이 파일은 헬름 차트 릴리즈 레코드와 관련된 모든 탬플릿 및 메타 정보를 포함하고 있습니다.

즉, 언제든 쿠버네티스 클러스터에 같은 설치를 다시 하고 싶으면 이 sh.helm.release.v1.mydb.v1 파일을 사용하면 됩니다.

또한 여기서 아래의 명령어를 다시 입력하면 새로운 릴리즈 정보가 생겨납니다.

  1. 헬름 업그레이드하기

    helm upgrade mydb bitnami/mysql --valeus values.yaml
  2. 헬름 설치 조회하기

    helm list
  3. 쿠버네티스 시크릿 조회하기

    kubectl get secrets -A

위에서 2, 3번 명령어를 입력하면 아래의 출력값이 나옵니다.

  1. 헬름 설치 조회하기

    NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
    mydb    default         2               2024-04-30 13:27:18.5188944 +0900 KST   deployed        mysql-10.1.1    8.0.36
  2. 쿠버네티스 시크릿 조회하기

    NAME                         TYPE                 DATA   AGE
    mydb-mysql                   Opaque               2      4m1s
    sh.helm.release.v1.mydb.v1   helm.sh/release.v1   1      4m1s
    sh.helm.release.v1.mydb.v2   helm.sh/release.v1   1      34s

헬름이 업데이트 되면서 2번 출력값에 있는 mydb의 REVISION이 2로 증가하였습니다. 동시에 쿠버네티스 시크릿에는 h.helm.release.v1.mydb.v2가 생겼습니다.

또한 아래의 명령어로 실제로 시크릿 내용을 조회할 수 있습니다.

kubectl get secret sh.helm.release.v1.mydb.v1 -o yaml

더 자세한 조회 부분은 Unchaptered (Blog) | 헬름 작동 과정(Helm in Action) # 헬름 동적 변수 조회하기에서 다루고 있습니다.

해당 문서에서 다루지 않는 부분이 추가되었습니다.

[JSON 형식으로 조회하기]

  1. 암호 조회

    각종 암호 정보는 유형마다 다를 수 있지만 일반적으로 base64 인코딩 되어 있어서, 디코딩을 해서 봐야함. base64 -d로 디코딩 가능

    1. mysql-password

      kubectl get secret mydb-mysql -o jsonpath=".data.mysql-password" | base64 -d
    2. mysql-root-password

      kubectl get secret mydb-mysql -o jsonpath=".data.mysql-root-password" | base64 -d
  2. 릴리즈 노트 조회

    릴리즈 정보는 gzip 압축되어 있음. 이를 풀기 위해서 gzip -c로 압축 해제 가능

    kubectl get secret sh.helm.release.v1.mydb.v1 -o jsonpath="{.data.release}" | gzip -c 

[YAML 형식으로 조회하기]

각 운영체제의 패키지 매니저를 통해서 yq를 설치해야 합니다.
저는 Windows여서 아래 명령어를 사용했습니다.

choco install yq
  1. 암호조회

    1. mysql-password

      kubectl get secret mydb-mysql -o yaml | yq e ".data.mysql-password" | base64 -d 
    2. mysql-root-password

      kubectl get secret mydb-mysql -o yaml | yq e ".data.mysql-root-password" | base64 -d
  2. 릴리즈 노트 조회

    커멘드를 찾지 못했습니다…

헬름 갯(helm get)

헬름에서 생성한 몇가지 요소들에 대한 스크립트

명령어 원본

명령어 설명

helm get notes <이름>

릴리즈 노트 조회

helm get values <이름>

사용한 동적 변수(--values) 확인

helm get values <이름> --all

동적 변수를 포함한 모든 변수 확인

helm get values <이름> --revision 1

헬름 리비전 1에서 사용된 동적 변수 (--values) 확인

helm get values <이름> --revision 1 --all

헬름 리비전 1에서 사용된 동적 변수를 포함한 모든 변수 확인

helm get manifest <이름>

쿠버네티스 탬플릿 파일(YAML Manifest) 확인

헬름 히스토리(helm history)

헬름 리스트(helm list)는 현재 가동 중인 릴리즈_네임 들의 가장 최신 상태를 보여줍니다.
헬림 히스토리(helm history)는 특정 릴리즈_네임의 모든 릴리즈 상태를 보여줍니다.

  1. bitnami/apache 설치하기

    helm install mywebserver bitnami/apache

  2. bitnami/apache 업데이트하면서 일부러 에러 일으키기

    helm upgrade mywebserver bitnami/apache --set image.pullPolicy=test

  3. helm history로 조회하기

    helm history mywebserver

  1. kubectl 환경변수 조회하기

    1. 명령어

      kubectl get secrets
    2. 미리보기

      NAME                                TYPE                 DATA   AGE
      sh.helm.release.v1.mywebserver.v1   helm.sh/release.v1   1      4m46s
      sh.helm.release.v1.mywebserver.v2   helm.sh/release.v1   1      91s

이제 이 결과를 기반으로 핼름 롤백을 진행할 수 있습니다.

핼름 롤백(helm rollback)

helm history로 본 특정 rivision으로 복구
이 챕터는 핼름 히스토리(helm history)에서 릴리즈를 삭제하지 않고 진행됩니다.

  1. 성공 릴리즈(REVISION == 1)으로 롤백

    helm rollback mywebserver 1

  2. 실패 릴리즈(REVISION == 2)으로 롤백

    helm rollback mywebserver 2

이후 아래 명령어들로 릴리즈에 대한 REVISION과 각 REVISION에 대한 릴리즈 메타 데이터를 조회할 수 있습니다.

  1. helm list

  2. helm history mywebserver

  3. kubectl get secrets

헬름 히스토리 보존(keep helm history)

헬름 릴리즈를 제거할 때, 히스토리를 보존해야 할 수 있습니다.
이 경우, --keep-history를 사용하여 보존할 수 있습니다.

  1. 헬름 릴리즈 삭제

    help uninstall mywebserver bitnami/apache --keep-history

헬름 네임스페이스(helm namespace)

쿠버네티스의 모든 구성요소들은 네임스페이스라는 단위로 격리되고 있습니다. 따라서 헬름 또한 네임스페이스를 사용하여 릴리즈를 생성 및 관리하고 있습니다.

여기서는 쿠버네티스 API인 kubectl을 사용하지 않고 헬름 CLI를 이용해서 네임스페이스를 제어하는 방법을 알아봅니다.

  1. bitnami/apache의 mywebserver 릴리즈 배포하기

    helm install mywebserver bitnami/apache --namespace mynamespace --create-namespace

  2. bitnami/apache의 mywebserver2 릴리즈 배포하기

    helm install mywebserver2 bitnami/apache --namespace mynamespace --create-namespace

  1. 모든 네임스페이스의 릴리즈 조회하기

    helm list -A

즉, 헬름은 릴리즈를 생성할 때 네임스페이스를 자동으로 생성할 수 있습니다. 하지만 릴리즈를 제거할 때 네임스페이스를 자동으로 제거하는 기능은 “헬름의 책임이 아니다”라는 이슈가 남아있습니다.

헬름 업그레이드 혹은 인스톨

CI/CD 파이프라인에서 현재 핼름 릴리즈가 Install인지 Upgrade인지 구분하는 것은 복잡합니다. 여기서는 그 해결방법을 알려줍니다.

  • helm upgrade --install mywebserver bitnami/apache

헬름 릴리즈 이름 자동 생성(helm generate names)

헬름 릴리즈 네임이 겹치는 것은 매우 피곤합니다.
따라서 난수를 활용하여 릴리즈 이름을 자동으로 생성하는 방법을 배워봅니다.

  1. 난수 이름 생성

    helm install bitnami/apache --generate-name

  1. 규격화된 난수 이름 생성

    아래에서 randAlpha 7 | lower을 randAlpha 7 | upper로 변경하면 작동하지 않는다. 그 이유는 kubernetes secrets에서 대문자가 허락되지 않기 때문이며, 자세한 에러는 직접 실행해보자.

    • 명령어

      helm install bitnami/apache --generate-name --name-template "mywebserver-{{ randAlpha 7 | lower }}"
    • 출력값

      NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
      mywebserver-gxklhci     default         1               2024-04-30 15:23:16.6869747 +0900 KST   deployed        apache-11.0.2   2.4.59

헬름 대기와 타임아웃(helm wait and timeout)

헬름은 기본적으로 가능한 빨리(ASAP) 쿠버네티스 API와 통신을하고 종료를 하게 됩니다. 즉, 실제 파드(Pod)가 작동되는 것을 기다리지 않습니다.

만약 기다리기를 원한다면 --wait 옵션을 사용하여 기다리게 할 수 있습니다.

이런 경우 기본 타임아웃은 5분(300초)이며 --timeout tm 10s 등을 이용해서 타임아웃 설정이 가능합니다.

CI/CD 파이프라인에서는 클러스터 생성을 고려하여 적절한 시간을 부여해야 합니다. 개인적으로는 10분 이상의 긴 시간을 추천합니다.

  1. 기본 시간 만큼 기다리게하기

    helm install bitnami/apache --generate-name --name-template “mywebserver-{{ randAlpha 7 | lower }}” --wait
  2. 10분 만큼 기다리게하기

    helm install bitnami/apache --generate-name --name-template “mywebserver-{{ randAlpha 7 | lower }}” --wait --timeout 10m

헬름 아토믹 인스톨(helm atomic install)

헬름이 업그레이드 릴리즈를 하다가 일부 부분 실패하면 어떻게 롤백할 수 있을까요? 여기서 배운 --atomic을 이용하여 이를 해결할 수 있습니다.

  1. 아토믹 배포

    helm install mywebserver bitnami/apache --atomic --wait --timeout 10m

헬름 강제 업데이트(helm forceful upgrades)

이 방법은 기존의 파드를 강제로 중단하면서 다운타임이 발생합니다.
CI/CD 파이프라인에서는 사용하지 않아야 하며, 특수한 요구사항이 있을 때에만 사용하세요. —force 명령어를 사용하면 됩니다.

  1. 강제 업데이트

    helm install mywebserver bitnami/apache --force

헬름 자동 청소(helm clean up on failure)

이 방법은 릴리즈 실패 시, 정말로 모든 요소가 삭제됩니다.
따라서 디버깅이 불가능해집니다. 이 옵션을 사용할거라면 헬름 실패와 관련한 메타 데이터를 뺴낼 추가적인 방법이 필요합니다.

--cleanup-on-failure 사용하면 됩니다.

  1. 자동 청소

    helm upgrade mywebserver bitnami/apache --cleanup-on-failure

실습

  1. Generate Name bitnami/tomcat

    helm install bitnami/tomcat --generate-name
  2. Dry run

    helm install bitnami/tomcat --generate-name --dry-run
  3. Templates

    helm template bitnami/tomcat
  4. Get Release Notes

    helm list
    
    helm get notes <RELEASE_NAME>
  5. Release Records

    helm list
    
    # Case 1
    kubectl get secret sh.helm.release.v1.<RELEASE_NAME>.v1 -o yaml | yq
    
    # Case 2
    kubectl get secrets
    kubectl get secret <SECRET_NAME> -o yaml | yq

Share article

Unchaptered