헬름 + 쿠버네티스 가이드북은 Bharath Thippireddy의 Helm Kubernetes Packaging Manager for Developers and DevOps을 보고 작성되었습니다.
가이드북의 전체 목차 및 인덱싱은 헬름 + 쿠버네티스 가이드북 문서를 참고해주세요.
사전 지식
필수 프로그램 설치 및 환경 구성
필수 프로그램 설치 및 환경 구성
Unchaptered (Blog) | 헬름 작동 과정(Helm in Action)에서 언급한 기본적인 Helm, Kubernetes-CLI(KUBECONFIG) 등이 완료된 상태로 진행해주세요.
헬름 고급 명령어
헬름 릴리즈 워크플로(Helm Release Worflow)
헬름 릴리즈 워크플로(Helm Release Workflow)
helm install mydb bitnami/mysql 명령어를 입력했을 때, 일어나는 일
대상 차트와 그 차트의 종속성을 불러오기
동적 변수 파일(values.yaml)을 파싱하기
두 파일을 기반으로 배포할 얌 파일 세트(YAMLs) 생성하기
차트(YAMLs)를 쿠버네티스 오브젝트에 파싱하고 유효성 검사 진행
차트(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가지 문제점을 알고 이를 극복해봅시다.
유효하지 않은 YAML 파일 형식의 존재
helm install mydb bitnami/mysql --values values.yaml --dry-run
명령어의 출력값은 YAML 형식과 100% 호환되지 않습니다.쿠버네티스 클러스터가 반드시 필요함
helm upgrade mydb bitnami/mysql --values values.yaml --dry-run
명령어는 install 이후에만 실행할 수 있습니다. 따라서 테스트를 진행하려면 배포가 완료되어 있어야 하며, Kubernetes Cluster와 항상 연결이 되어 있어야 합니다.
따라서 이런 문제를 해결하기 위해서 helm template <LOCAL> <HOST>/<TARGET>
명령어가 나왔습니다. 헬름 탬플릿의 주요 특징은 다음과 같습니다.
[장점] 항상 유효한 YAML 파일 반환
[장점] 쿠버네티스 클러스터가 필요 없음
[단점] 유효성 검사를 진행하지 않음
헬름 드라이런(--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)
헬름 설치하기
helm install mydb bitnami/mysql --valeus values.yaml
헬름 설치 조회하기
helm ls
쿠버네티스 시크릿 조회하기
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 파일을 사용하면 됩니다.
또한 여기서 아래의 명령어를 다시 입력하면 새로운 릴리즈 정보가 생겨납니다.
헬름 업그레이드하기
helm upgrade mydb bitnami/mysql --valeus values.yaml
헬름 설치 조회하기
helm list
쿠버네티스 시크릿 조회하기
kubectl get secrets -A
위에서 2, 3번 명령어를 입력하면 아래의 출력값이 나옵니다.
헬름 설치 조회하기
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
쿠버네티스 시크릿 조회하기
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 형식으로 조회하기]
암호 조회
각종 암호 정보는 유형마다 다를 수 있지만 일반적으로 base64 인코딩 되어 있어서, 디코딩을 해서 봐야함. base64 -d로 디코딩 가능
mysql-password
kubectl get secret mydb-mysql -o jsonpath=".data.mysql-password" | base64 -d
mysql-root-password
kubectl get secret mydb-mysql -o jsonpath=".data.mysql-root-password" | base64 -d
릴리즈 노트 조회
릴리즈 정보는 gzip 압축되어 있음. 이를 풀기 위해서 gzip -c로 압축 해제 가능
kubectl get secret sh.helm.release.v1.mydb.v1 -o jsonpath="{.data.release}" | gzip -c
[YAML 형식으로 조회하기]
각 운영체제의 패키지 매니저를 통해서 yq를 설치해야 합니다.
저는 Windows여서 아래 명령어를 사용했습니다.choco install yq
암호조회
mysql-password
kubectl get secret mydb-mysql -o yaml | yq e ".data.mysql-password" | base64 -d
mysql-root-password
kubectl get secret mydb-mysql -o yaml | yq e ".data.mysql-root-password" | base64 -d
릴리즈 노트 조회
커멘드를 찾지 못했습니다…
헬름 갯(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)는 특정 릴리즈_네임의 모든 릴리즈 상태를 보여줍니다.
bitnami/apache 설치하기
helm install mywebserver bitnami/apache
bitnami/apache 업데이트하면서 일부러 에러 일으키기
helm upgrade mywebserver bitnami/apache --set image.pullPolicy=test
helm history로 조회하기
helm history mywebserver
kubectl 환경변수 조회하기
명령어
kubectl get secrets
미리보기
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)에서 릴리즈를 삭제하지 않고 진행됩니다.
성공 릴리즈(REVISION == 1)으로 롤백
helm rollback mywebserver 1
실패 릴리즈(REVISION == 2)으로 롤백
helm rollback mywebserver 2
이후 아래 명령어들로 릴리즈에 대한 REVISION과 각 REVISION에 대한 릴리즈 메타 데이터를 조회할 수 있습니다.
helm list
helm history mywebserver
kubectl get secrets
헬름 히스토리 보존(keep helm history)
헬름 릴리즈를 제거할 때, 히스토리를 보존해야 할 수 있습니다.
이 경우,--keep-history
를 사용하여 보존할 수 있습니다.
헬름 릴리즈 삭제
help uninstall mywebserver bitnami/apache --keep-history
헬름 네임스페이스(helm namespace)
쿠버네티스의 모든 구성요소들은 네임스페이스라는 단위로 격리되고 있습니다. 따라서 헬름 또한 네임스페이스를 사용하여 릴리즈를 생성 및 관리하고 있습니다.
여기서는 쿠버네티스 API인 kubectl을 사용하지 않고 헬름 CLI를 이용해서 네임스페이스를 제어하는 방법을 알아봅니다.
bitnami/apache의 mywebserver 릴리즈 배포하기
helm install mywebserver bitnami/apache --namespace mynamespace --create-namespace
bitnami/apache의 mywebserver2 릴리즈 배포하기
helm install mywebserver2 bitnami/apache --namespace mynamespace --create-namespace
모든 네임스페이스의 릴리즈 조회하기
helm list -A
즉, 헬름은 릴리즈를 생성할 때 네임스페이스를 자동으로 생성할 수 있습니다. 하지만 릴리즈를 제거할 때 네임스페이스를 자동으로 제거하는 기능은 “헬름의 책임이 아니다”라는 이슈가 남아있습니다.
헬름 업그레이드 혹은 인스톨
CI/CD 파이프라인에서 현재 핼름 릴리즈가 Install인지 Upgrade인지 구분하는 것은 복잡합니다. 여기서는 그 해결방법을 알려줍니다.
helm upgrade --install mywebserver bitnami/apache
헬름 릴리즈 이름 자동 생성(helm generate names)
헬름 릴리즈 네임이 겹치는 것은 매우 피곤합니다.
따라서 난수를 활용하여 릴리즈 이름을 자동으로 생성하는 방법을 배워봅니다.
난수 이름 생성
helm install bitnami/apache --generate-name
규격화된 난수 이름 생성
아래에서 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분 이상의 긴 시간을 추천합니다.
기본 시간 만큼 기다리게하기
helm install bitnami/apache --generate-name --name-template “mywebserver-{{ randAlpha 7 | lower }}” --wait
10분 만큼 기다리게하기
helm install bitnami/apache --generate-name --name-template “mywebserver-{{ randAlpha 7 | lower }}” --wait --timeout 10m
헬름 아토믹 인스톨(helm atomic install)
헬름이 업그레이드 릴리즈를 하다가 일부 부분 실패하면 어떻게 롤백할 수 있을까요? 여기서 배운
--atomic
을 이용하여 이를 해결할 수 있습니다.
아토믹 배포
helm install mywebserver bitnami/apache --atomic --wait --timeout 10m
헬름 강제 업데이트(helm forceful upgrades)
이 방법은 기존의 파드를 강제로 중단하면서 다운타임이 발생합니다.
CI/CD 파이프라인에서는 사용하지 않아야 하며, 특수한 요구사항이 있을 때에만 사용하세요.—force
명령어를 사용하면 됩니다.
강제 업데이트
helm install mywebserver bitnami/apache --force
헬름 자동 청소(helm clean up on failure)
이 방법은 릴리즈 실패 시, 정말로 모든 요소가 삭제됩니다.
따라서 디버깅이 불가능해집니다. 이 옵션을 사용할거라면 헬름 실패와 관련한 메타 데이터를 뺴낼 추가적인 방법이 필요합니다.
--cleanup-on-failure
사용하면 됩니다.
자동 청소
helm upgrade mywebserver bitnami/apache --cleanup-on-failure
실습
Generate Name bitnami/tomcat
helm install bitnami/tomcat --generate-name
Dry run
helm install bitnami/tomcat --generate-name --dry-run
Templates
helm template bitnami/tomcat
Get Release Notes
helm list helm get notes <RELEASE_NAME>
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