이번 포스팅은 kubernetes app을 다양한 환경에서 배포할 수 있도록 Helm화 한 뒤
Jenkins로 배포하는 과정에서 어떻게 Helm 패키지를 생성하고 배포하는지 정리한 글이다.
Helm 설치
# Helm 설치
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
Helm 템플릿 생성 (init)
# jenkins 유저로 전환
su - jenkins -s /bin/bash
# 템플릿 생성
helm create api-tester
당장 필요없는 내용 삭제하기 (deleting)
헬름 템플릿을 생성하면 위와 같은 구조의 차트 directory가 생성된다.
우리는 지금 단일 App을 배포하고, 별다른 테스트 모듈이 없으므로 charts 디렉토리와 testes 디렉토리를 제외해도 된다.
각 디렉토리 및 파일에 대한 설명은 아래와 같다.
- charts: 마이크로서비스 환경에서 여러개의 App을 배포하여 하나의 서비스를 이룬다고 가정할 때, 각 app에 대한 yaml 파일을 개별로 관리하기 위한 서브차트 폴더
- tests: 메인 App이 배포된 후 통신상태를 확인하기 위함
- _helpers.tpl: 사용자 정의 전역변수 선언
{{- define "api-tester.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
- NOTES.txt: git 저장소의 README 역할을 한다. 배포 후에 해당 파일의 내용으로 안내문구를 출력한다.
- .helmignore: .gitignore와 유사하게 helm이 배포되어 렌더링 시 제외할 파일을 지정한다. 주로 현재 에러를 발생시키는 파일을 제외시킬 때 유용하다.
- Chart.yaml: 차트의 기본 메타데이터를 선언
apiVersion: v2
name: api-tester
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.16.0"
- values.yaml: 배포할 yaml 파일에 들어갈 변수들의 기본값 선언
replicaCount: 1
image:
repository: nginx
pullPolicy: IfNotPresent
tag: ""
service:
type: ClusterIP
port: 80
내 yaml 파일에 맞게 Helm Package 수정하기 (modify)
helm 패키지를 선언하면 templates 디렉토리 하위에 쿠버네티스 매니페스트 형식의 기본 yaml 파일들이 생성된다.
해당 yaml 파일을 우리가 배포하고자 하는 app에 대한 속성을 정의한 yaml 파일로 helm이 배포할 수 있도록 수정해야 한다.
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: anotherclass-222
name: api-tester-2221
labels:
part-of: k8s-anotherclass
component: backend-server
name: api-tester
instance: api-tester-2221
version: 1.0.0
managed-by: kubectl
spec:
selector:
matchLabels:
part-of: k8s-anotherclass
component: backend-server
name: api-tester
instance: api-tester-2221
replicas: 2
strategy:
type: RollingUpdate
template:
metadata:
labels:
part-of: k8s-anotherclass
component: backend-server
name: api-tester
instance: api-tester-2221
version: 1.0.0
spec:
nodeSelector:
kubernetes.io/hostname: k8s-master
containers:
- name: api-tester-2221
image: 1pro/api-tester:v1.0.0
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
envFrom:
- configMapRef:
name: api-tester-2221-properties
startupProbe:
httpGet:
path: "/startup"
port: 8080
periodSeconds: 5
failureThreshold: 24
readinessProbe:
httpGet:
path: "/readiness"
port: 8080
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: "/liveness"
port: 8080
periodSeconds: 10
failureThreshold: 3
resources:
requests:
memory: "100Mi"
cpu: "100m"
limits:
memory: "200Mi"
cpu: "200m"
volumeMounts:
- name: secret-datasource
mountPath: /usr/src/myapp/datasource
volumes:
- name: secret-datasource
secret:
secretName: api-tester-2221-postgresql
먼저 기본으로 생성된 deployment.yaml 파일을 살펴보자.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "api-tester.fullname" . }}
labels:
{{- include "api-tester.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "api-tester.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "api-tester.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "api-tester.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
해당 yaml 파일에서 주요한 키워드를 포함하고 있는 부분을 추려보면 다음과 같다.
metadata:
name: {{ include "api-tester.fullname" . }}
labels:
{{- include "api-tester.labels" . | nindent 4 }}
...
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
helm은 위와 같이 {{ }} 이중 꺾쇠 괄호 내부에 있는 텍스트를 적절한 값으로 치환하여 yaml 파일을 구성한다.
괄호 내부에 포함된 텍스트 중 자주 출현하는 키워드 3개의 의미는 다음과 같다.
- include: _helpers.tpl에서 정의한 전역변수 참조
- .Chart: Chart.yaml 에서 정의한 차트 메타데이터 참조
- .Values: values.yaml에서 정의한 속성값들을 참조
이전 섹션인 deleting에서 기술한 각 파일에 대한 예시와 비교하며 값을 치환해 보면 이해가 빠를 수 있다.
예를 들어 {{ .Values.service.port }}는 80으로 치환된다.
값을 치환할 때는 단일 text가 아니라 정의된 하위 block의 값을 모두 가져온다.
# deployment.yaml
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
deployment.yaml에서 container 헬스체크 용도인 probe 정의에 helm template을 위와 같이 작성한 후,
# values.yaml
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
values.yaml에서 위와 같이 작성하면 각 probe 설정은 httpGet부터 그 하위 속성인 path, port의 key-value 값까지 모두 가져와 치환되기 때문에 각 속성을 일일이 helm 변수로 할당하지 않고 blcok 단위로 작성하여 편리하게 배포할 수 있다.
위 3가지 주요 keyword를 말고도 .Release라는 키워드가 존재한다.
이 Release는 helm install로 차트를 배포할 때 주어지는 parameter 값을 가져오는 용도로 사용된다.
# helm install <NAME> <CHART_PATH> -f <VALUES_PATH> -n <NAMESPACE>
helm install api-tester-2221 ./api-tester -n anotherclass-222
위와 같이 App 이름과 네임스페이스를 지정하여 helm 차트를 배포한다고 했을 때, 다음과 같이 치환된다.
- {{ .Release.Name }} => api-tester-2221
- { .Release.Namespace }} => anotherclass-222
- {{ .Release.service }} => helm
해당 형식을 이용하여 deployment.yaml과 values.yaml을 수정하면 다음과 같이 수정할 수 있다.
중요한 것은 굳이 모든 yaml 파일의 속성을 helm 변수화 시킬 필요도 없고, 때로는 하드코딩으로 박아넣어도 된다!
Deployment.yaml 수정
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "api-tester.fullname" . }}
labels:
{{- include "api-tester.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "api-tester.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "api-tester.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "api-tester.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
startupProbe:
httpGet:
path: "/startup"
port: 8080
periodSeconds: 5
failureThreshold: 24
livenessProbe:
httpGet:
path: "/liveness"
port: 8080
readinessProbe:
httpGet:
path: "/readiness"
port: 8080
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
values.yaml 수정
# Default values for api-tester.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 2
image:
repository: 1pro/api-tester:v1.0.0
pullPolicy: Always
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: false
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: NodePort
port: 80
nodePort: 32221
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources:
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
requests:
memory: "100Mi"
cpu: "100m"
limits:
memory: "200Mi"
cpu: "200m"
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
내 resource 더 추가하기 (addition)
기본으로 생성되는 yaml 파일들 말고 우리의 App에는 추가적인 쿠버네티스 instance가 필요할 수 있다.
그럴 때는 templates 하위에 필요한 yaml 파일을 생성하고 이전 섹션의 작업을 수행하면 된다.
예를 들어 가장 많이 사용되는 configmap과 secret이 기본적으로 생성되지 않기 때문에
우리가 추가로 생성하면 다음과 같이 작성할 수 있다.
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "api-tester.fullname" . }}-properties
labels:
{{- include "api-tester.labels" . | nindent 4 }}
data:
{{- toYaml .Values.configmap.data.properties | nindent 2 }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ include "api-tester.fullname" . }}-postgresql
labels:
{{- include "api-tester.labels" . | nindent 4 }}
stringData:
{{- toYaml .Values.secret.data.postgresql | nindent 2 }}
configmap:
data:
properties:
spring_profiles_active: "dev"
application_role: "ALL"
postgresql_filepath: "/usr/src/myapp/datasource/postgresql-info.yaml"
secret:
data:
postgresql:
postgresql-info.yaml: |
driver-class-name: "org.postgresql.Driver"
url: "jdbc:postgresql://postgresql:5431"
username: "dev"
password: "dev123"
configmap.yaml, secret.yaml을 생성하여 helm template으로 만든 뒤,
values.yaml에 configmap과 secret에 들어갈 항목들을 적어주면 완성이다.
Helm 배포하기
Helm 차트를 배포하기 전에 우리가 작성한 문서들이 helm 문법에 잘 맞는지 확인해야 한다.
helm template ./api-tester
# 출력 결과
---
# Source: api-tester/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: release-name-api-tester-postgresql
labels:
helm.sh/chart: api-tester-0.1.0
app.kubernetes.io/name: api-tester
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
stringData:
postgresql-info.yaml: |-
driver-class-name: "org.postgresql.Driver"
url: "jdbc:postgresql://postgresql:5431"
username: "dev"
password: "dev123"
---
# Source: api-tester/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: release-name-api-tester
labels:
helm.sh/chart: api-tester-0.1.0
app.kubernetes.io/name: api-tester
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: "1.16.0"
app.kubernetes.io/managed-by: Helm
spec:
type: NodePort
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: api-tester
app.kubernetes.io/instance: release-name
helm template 명령어를 이용하면 지정한 helm 차트에 대한 yaml 파일들을 values.yaml의 값으로 치환되어 실제로 kubernetes에 어떻게 배포되는지 확인할 수 있다.
template이 의도한 대로 잘 작성 됐다면 helm 배포를 수행한다.
helm upgrade api-tester-2221 ./api-tester -n anotherclass-222 --create-namespace --install
helm install 대신 upgrade를 사용하고 --install 옵션을 사용하게 되면
기존에 동일한 이름의 app이 update 되고 만약 동일한 이름의 app이 없다면 새로 만들어 준다.
--create-namespace 옵션 또한 네임스페이스가 없다면 새로 만들어준다.
helm install => kubectl create
helm upgrade => kubectl patch
'DevOps' 카테고리의 다른 글
[ArgoCD] ArgoCD로 쿠버네티스 클러스터에 App 배포하기 (4) | 2024.10.18 |
---|---|
[ArgoCD] ArgoCD 아키텍처에 대한 이해 (0) | 2024.10.17 |
[Jenkins] Jenkins Pipeline 구축 (기초부터 Blue/Green 배포까지) (0) | 2024.05.31 |
[DevOps] 데브옵스 환경 구축 (0) | 2024.05.12 |
[DevOps] 데브옵스 한방 정리 (0) | 2024.05.08 |