이번에는 실제로 마이크로서비스들을 배포하기 위한 helm chart를 직접 만들어보도록 하자. Chart를 만들기 전에, 실제로 어떤 목적으로 패키징할지 고민해봐야 한다. 이번 실습에서는 한국에서 자주 사용하는 Spring Boot 기반 애플리케이션을 배포하기 위한 helm chart를 만들어보자.
실습에 대한 파일들은 Github에 올려두었습니다.
https://github.com/beer-one/charts
차트 이름 결정하기
먼저 차트 이름을 결정해야 한다. 일단 차트를 만들 디렉터리를 하나 생성하고, 그 하위에 사용할 차트 이름과 같은 이름으로 디렉터리를 하나 더 생성하자. 그리고 그 하위에 Chart.yaml과 values.yaml 파일과 template 디렉터리를 만들자. 일단 차트 이름은 spring-app으로 하자.
.
└── spring-app
├── Chart.yaml
├── templates
└── values.yaml
그리고 Chart.yaml에는 차트에 해당하는 메타데이터를 작성하면 된다.
apiVersion: v2
name: spring-app
version: 0.1.0
appVersion: 0.1.0
kubeVersion: ">= 1.27.0"
description: "Spring application server"
home: https://chart.beerone.com
maintainers:
- name: beerone
email: floidea2@gmail.com
- Helm 3의 경우
apiVersion
은 v2이다. 최신버전을 사용하자. name
에는 차트 이름을 넣으면 된다. 차트를 배포할 때 디렉터리 이름과 별개로 Chart.yaml에 이름이 진짜 차트 이름이 된다.version
은 차트의 버전이다.appVersion
은 차트에서 배포할 애플리케이션의 버전이다.home
은 차트를 배포할 외부 레지스트리를 적어두는 설명란이다. (필수는 아님)maintainers
는 차트를 개발한 사람의 정보를 적어두는 곳이다. (필수는 아님)
여기서 version
과 appVersion
에 대해 헷갈릴 수도 있는데 version
은 차트의 실제 버전이다. 차트도 컨테이너 이미지와 마찬가지로 버전 관리를 할 수 있는데 --version
옵션을 줘서 차트의 특정 버전으로 배포를 할 수 있다.
$ helm install argocd argo/argo-cd --version 5.41.0
반면 appVersion
은 차트에서 배포할 애플리케이션의 버전, 즉 실제로 배포될 애플리케이션의 컨테이너 이미지 버전을 명시하기 위해 사용한다. Argo chart의 version과 appVersion을 예시로 보면 argocd의 차트 버전은 5.45.0이고 실제 argocd 컨테이너 이미지는 v2.8.2를 사용한다는 뜻이다.
$ helm repo add argo https://argoproj.github.io/argo-helm
$ helm search repo argo
helm search repo argo
NAME CHART VERSION APP VERSION DESCRIPTION
argo/argo 1.0.0 v2.12.5 A Helm chart for Argo Workflows
argo/argo-cd 5.45.0 v2.8.2 A Helm chart for Argo CD, a declarative, GitOps...
argo/argo-ci 1.0.0 v1.0.0-alpha2 A Helm chart for Argo-CI
argo/argo-events 2.4.0 v1.8.0 A Helm chart for Argo Events, the event-driven ...
argo/argo-lite 0.1.0 Lighweight workflow engine for Kubernetes
argo/argo-rollouts 2.31.6 v1.5.1 A Helm chart for Argo Rollouts
argo/argo-workflows 0.33.1 v3.4.10 A Helm chart for Argo Workflows
argo/argocd-applicationset 1.12.1 v0.4.1 A Helm chart for installing ArgoCD ApplicationSet
argo/argocd-apps 1.4.1 A Helm chart for managing additional Argo CD Ap...
argo/argocd-image-updater 0.9.1 v0.12.2 A Helm chart for Argo CD Image Updater, a tool ...
argo/argocd-notifications 1.8.1 v1.2.1 A Helm chart for ArgoCD notifications, an add-o...
필요 리소스 결정하기
일단 스프링 애플리케이션을 배포할 때 최소한으로 필요한 조건을 세워야 한다. 먼저 당연하겠지만 애플리케이션 배포를 위해서는 Deployment는 반드시 필요하다. 그리고 다른 파드로부터 요청을 받는 파드의 경우 Service가 필요할 것이다. 그리고 Spring Cloud Kubernetes 디펜던시를 사용하는 애플리케이션이라면 RBAC 리소스가 필요하며, ConfigMap이나 Secret으로부터 프로퍼티를 얻는다면 ConfigMap, Secret도 필요하다. 마지막으로 게이트웨이와 같이 쿠버네티스 외부로 노출되어야 하는 애플리케이션이라면 Ingress도 붙여줘야 한다. (NodePort, LoadBalancer 타입 서비스는 무시하자.)
이런식으로 배포에 필요한 리소스들을 정의했다면 차트의 template
하위에 파일들을 만들어두자.
.
└── spring-app
├── Chart.yaml
├── templates
│ ├── clusterRoleBinding.yaml
│ ├── configmap.yaml
│ ├── deployment.yaml
│ ├── ingress.yaml
│ ├── role.yaml
│ ├── roleBinding.yaml
│ ├── secret.yaml
│ ├── service.yaml
│ └── serviceAccount.yaml
└── values.yaml
Spring Cloud Kubernetes에 대한 내용이 알고싶다면 다음 글을 참고해주세요.
2023.09.23 - [[개발] Spring Framework/Spring Cloud] - Spring Cloud Kubernetes [1] 소개 및 PropertySource
각 쿠버네티스 리소스 템플릿화 하기
이제 template 하위의 쿠버네티스 리소스들을 템플릿화 해보자.
deployment.yaml
먼저 무조건 필수인 deployment.yaml부터 템플릿화 해보자.
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: {{ .Values.app.name }}
name: {{ .Values.app.name }}
namespace: {{ .Release.Namespace | quote }}
spec:
replicas: {{ .Values.app.replicas }}
selector:
matchLabels:
app: {{ .Values.app.name }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml .Values.podAnnotations | nindent 8 }}
{{- end }}
labels:
app: {{ .Values.app.name }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- if .Values.rbac.enabled }}
serviceAccount: {{ .Values.app.name }}
{{- end }}
containers:
- image: {{ .Values.app.image }}
name: {{ .Values.app.name }}
ports:
- name: http
protocol: TCP
containerPort: {{ .Values.app.port }}
{{- with .Values.app.customCommand }}
command:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.app.customArgs }}
args:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.app.env }}
env:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.app.resources }}
resources:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.probes.readiness }}
readinessProbe:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.probes.liveness }}
livenessProbe:
{{- toYaml . | nindent 10 }}
{{- end }}
내용이 너무 길어서 부분부분 잘라서 익혀보자.
metadata, spec
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: {{ .Values.app.name }}
name: {{ .Values.app.name }}
namespace: {{ .Release.Namespace | quote }}
spec:
replicas: {{ .Values.app.replicas }}
selector:
matchLabels:
app: {{ .Values.app.name }}
일단 템플릿 파일에서 values.yaml 파일의 변수에 접근하려면 .Values
프리픽스를 사용해야 한다.
먼저 메타데이터 부분이다. 차트에서는 배포되는 애플리케이션 이름을 values.yaml에 app.name
이라는 변수로 받을 것이다. 그리고 Deployment에서 레이블셀렉터로 사용할 기본 레이블인 app 레이블을 app.name
값으로 바인딩한다. 그리고 네임스페이스는 .Release.Namespace
로 부여하는데 이 값은 helm install 명령 시 --namespace
로 설정한 네임스페이스의 값으로 바인딩할 때 사용하는 변수이다.
spec 부분에서는 레이블셀렉터 부분을 app 레이블로 설정하였다. 그리고 레플리카 갯수도 배포할 애플리케이션마다 다를 수 있기 때문에 app.replicas
로 변수화 하였다.
podTemplate
...
spec:
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml .Values.podAnnotations | nindent 8 }}
{{- end }}
labels:
app: {{ .Values.app.name }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- if .Values.rbac.enabled }}
serviceAccount: {{ .Values.app.name }}
{{- end }}
containers:
- image: {{ .Values.app.image }}
name: {{ .Values.app.name }}
ports:
- name: http
protocol: TCP
containerPort: {{ .Values.app.port }}
{{- with .Values.app.customCommand }}
command:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.app.customArgs }}
args:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.app.env }}
env:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.app.resources }}
resources:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.probes.readiness }}
readinessProbe:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.probes.liveness }}
livenessProbe:
{{- toYaml . | nindent 10 }}
{{- end }}
이번에는 podTemplate에 대한 설정이다. 여기서 metadata에는 파드에 부여할 어노테이션과 레이블을 정의한다.. 기본적으로 Deployment의 레이블셀렉터 대상인 app 레이블은 필수로 들어가고 추가 레이블과 어노테이션은 각각 .podLabels
, .podAnnotations
변수에 설정할 수 있다. 여기서 with
와 toYaml
은 뒤에서 따로 다루겠다.
spec 부분에서는 컨테이너 이미지와 이름, 포트번호를 각각 .app.image
, .app.name
, .app.port
로 변수화 하였다. 차트에서 애플리케이션 고유의 값은 변수화가 필요하다.
if / else
애플리케이션 중에 Spring Cloud Kubernetes를 사용하는 애플리케이션의 경우 쿠버네티스 리소스 접근을 위해 RBAC 권한이 필요하다. 그런 애플리케이션의 경우 파드에 ServiceAccount를 부여해야 하는데 여기서는 if
문을 사용하여 .rbac.enabled
변수로 ServiceAccounts를 부여할지 결정할 수 있다.
if문은 다음과 같이 사용할 수 있다.
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}
PIPELINE
에는 true / false 값이 올 수 있다. 다음의 경우 false로 판단한다.
- Boolean: false
- Integer: 0
- String: ""
- Object: nil (빈 값 or null)
- Empty Collection
그리고 PIPELINE
에는 연산자 또한 들어갈 수 있다.
{{- if eq .Values.app.replica 1 }}
{{- if has 5 .Values.intArray }}
- 연산자, 변수, 값 순이다.
- eq, gt, ge, lt, le, ne, has 등이 있다.
with, toYaml
그리고 위에는 with 문이 많이 있다. with문은 값이 true일 때 해당 값으로 스코프를 변경하는 것을 말한다.
{{ with PIPELINE }}
...
{{ end }}
여기서 스코프란 .
의 현재 위치라고 생각하면 된다. .
은 우리가 흔히 사용하는 상대경로와 비슷한 개념으로 이해하면 된다. 그래서 with, end를 감싸는 부분에서는 with 뒤에 설정된 값으로 스코프가 변경된다. 여기서 명심해야 할 것은 with, end를 감싸는 부분에서는 기본적으로는 Values
값에 접근할 수 없다.
그리고 toYaml은 해당 변수 값을 yaml 포멧으로 변환해주는 기능을 한다. 그래서 보통 toYaml은 with문과 함께 복잡한 Array나 Dict 변수를 템플릿에 바인딩하기 위해 사용한다.
그러면 실제로 값이 어떻게 바인딩되는지 확인해보기 위해 values.yaml을 다음과 같이 생성해보자.
app:
name: todo-api
replicas: 1
image: beer1/todo-server-kotlin:0.1.0
port: 9000
customCommand: []
customArgs: []
env: []
resources:
requests:
cpu: 500m
memory: 256Mi
limits:
cpu: 500m
memory: 256Mi
podAnnotations: {}
podLabels: {}
probes:
readiness: {}
liveness: {}
rbac:
enabled: false
여기서는 필수 값인 app.name
, app.replicas
, app.image
, app.port
과 app.resources
만 값을 부여하였다. helm template 명령어로 변수 바인딩이 어떻게 되는지 확인해보자. 리소스가 정상적으로 바인딩 된 것을 확인할 수 있다.
$ helm template todo-api ./spring-app -n todo -n todo
---
# Source: spring-app/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: todo-api
name: todo-api
namespace: "todo"
spec:
replicas: 1
selector:
matchLabels:
app: todo-api
template:
metadata:
labels:
app: todo-api
spec:
containers:
- image: beer1/todo-server-kotlin:0.1.0
name: todo-api
ports:
- name: http
protocol: TCP
name: http
containerPort: 9000
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 500m
memory: 256Mi
values.yaml 기본값 부여
차트의 values.yaml은 기본값을 의미한다. 실제로 helm 배포 시 --set
옵션을 사용하거나 -f
옵션을 사용하여 변수를 바인딩하지만 설정하지 않은 변수의 경우 차트의 values.yaml에 있는 값으로 자동 바인딩 된다. 그래서 기본값 설정은 매우 중요한 부분인데 사용자가 변수 값을 설정하지 않아도 의도한 대로 돌아가도록 기본값을 잘 설정해야 한다. 그래서 보통 필수값을 제외한 나머지 값들은 제로값으로 설정하는 것이 좋다. (Bool: false, String: "", Dict: {}, Array: [])
일단 지금까지 values.yaml은 이렇게 구성하였다.
app:
name: todo-api
replicas: 1
image: beer1/todo-server-kotlin:0.1.0
port: 9000
customCommand: []
customArgs: []
env: []
resources: {}
podAnnotations: {}
podLabels: {}
probes:
readiness: {}
liveness: {}
rbac:
enabled: false
service.yaml
다른 파드로부터 요청을 받는 파드의 경우 Service가 필요하다. 그래서 Service는 선택적으로 만들어져야 한다. yaml파일 전체를 if문으로 감싸서 Service 자체를 선택적으로 만들어 낼 수 있다.
{{- if .Values.service.enabled }}
apiVersion: v1
kind: Service
metadata:
labels:
app: {{ .Values.app.name }}
name: {{ .Values.app.name }}
namespace: {{ .Release.Namespace | quote }}
spec:
ports:
- port: {{ .Values.service.port }}
protocol: TCP
name: http
targetPort: {{ .Values.app.port }}
selector:
app: {{ .Values.app.name }}
{{- end }}
- Service의 이름, 레이블, 레이블셀렉터는 모두
app.name
으로 생성했다. 특히 레이블셀렉터의 경우에는 무조건 파드의 고유 레이블인app.name
으로 설정해야 한다. - targetPort도 마찬가지로 파드의 포트와 일치해야 하므로
app.port
로 설정해야 한다.
추가된 Service에 대한 기본값도 구성해보자. 필요한 애플리케이션에만 서비스를 부여하면 되니까 service.enabled
는 false로 하고, 보통 http 포트는 80을 사용하므로 service.port
의 기본값은 80으로 하자.
values.yaml
app:
name: todo-api
replicas: 1
image: beer1/todo-server-kotlin:0.1.0
port: 9000
customCommand: []
customArgs: []
env: []
resources: {}
podAnnotations: {}
podLabels: {}
probes:
readiness: {}
liveness: {}
rbac:
enabled: false
service:
enabled: true
port: 80
ingress.yaml
게이트웨이와 같이 쿠버네티스 외부로 노출되어야 하는 애플리케이션이라면 Ingress도 붙여줘야 한다. ingress도 선택적으로 만들어지도록 템플릿을 만들자.
{{- if .Values.service.enabled }}
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Values.app.name }}
namespace: {{ .Release.Namespace | quote }}
spec:
ingressClassName: {{ .Values.ingress.className }}
- host: {{ .Values.ingress.host }}
http:
paths:
- path: {{ .Values.ingress.path }}
pathType: {{ .Values.ingress.pathType }}
backend:
service:
name: {{ .Values.app.name }}
port:
number: {{ .Values.service.port }}
{{- end }}
{{- end }}
- Ingress는 Service가 있어야 하기 때문에
service.enabled
도 true여야 한다. - Ingress를 연동하려면 사용할 ingressClassName을 설정해야 한다. 여기서는
ingress.className
으로 값을 부여할 수 있다. - 요청 받을 host와 path, pathType을 각각
ingress.host
,ingress.path
,ingress.pathType
으로 변수화 시켰다. - 라우팅할 백엔드는 당연히 차트에서 만들어낸 서비스 정보여야 한다.
Ingress의 기본값도 설정해보자. ingress 생성도 선택적이어야 하므로 ingress.enabled
는 false여야 하고, 자주 사용되는 ingressClassName은 아무래도 nginx기 때문에 ingress.className
의 기본값은 nginx로 하고 라우팅 pathType도 보통 prefix를 사용하므로 ingress.pathType
의 기본값은 Prefix로 하자.
values.yaml
app:
name: todo-api
replicas: 1
image: beer1/todo-server-kotlin:0.1.0
port: 9000
customCommand: []
customArgs: []
env: []
resources: {}
podAnnotations: {}
podLabels: {}
probes:
readiness: {}
liveness: {}
rbac:
enabled: false
service:
enabled: true
port: 80
ingress:
enabled: false
className: nginx
host: ""
path: ""
pathType: Prefix
RBAC
Spring Cloud Kuberentes를 사용하는 애플리케이션의 경우 Service, ConfigMap, Secret을 조회할 수 있어야 하므로 RBAC 권한이 필요하다. RBAC 권한은 ServiceAccount, Role(ClusterRole), RoleBinding(ClusterRoleBinding)을 생성하면 된다.
여기서는 Spring Cloud Kubernetes에서 필요한 기본적인 Role을 선택적으로 생성할 수 있고, 기존에 있는 Role, ClusterRole을 바인딩 시킬 수도 있도록 구성해보자.
serviceAccount.yaml
{{- if .Values.rbac.enabled }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.app.name }}
namespace: {{ .Release.Namespace | quote }}
{{- end }}
role.yaml
{{- if .Values.rbac.enabled }}
{{- if .Values.rbac.roleBinding.autoCreate }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: spring-application
namespace: {{ .Release.Namespace | quote }}
rules:
- apiGroups: [""]
resources: ["pods", "configmaps", "secrets", "services", "endpoints"]
verbs: ["get", "watch", "list"]
{{- end }}
{{- end }}
rbac.roleBinding.autoCreate
변수를 사용하여 Spring Cloud Kuberentes에서 필요한 기본적인 role을 생성할지 결정하는 조건을 추가하였다.
roleBinding.yaml
{{- $root := . -}}
{{- if .Values.rbac.enabled }}
{{- if .Values.rbac.roleBinding.autoCreate }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: spring-role-{{ .Values.app.name }}
namespace: {{ .Release.Namespace | quote }}
subjects:
- kind: ServiceAccount
name: {{ .Values.app.name }}
apiGroup: ""
roleRef:
kind: Role
name: spring-application
apiGroup: rbac.authorization.k8s.io
{{- end }}
---
{{- range .Values.rbac.roleBinding.additionalRoles }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "{{ . }}-{{ $root.Values.app.name }}"
namespace: {{ $root.Release.Namespace | quote }}
subjects:
- kind: ServiceAccount
name: {{ $root.Values.app.name }}
apiGroup: ""
roleRef:
kind: Role
name: {{ . }}
apiGroup: rbac.authorization.k8s.io
---
{{- end }}
{{- range .Values.rbac.roleBinding.additionalClusterRoles }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "{{ . }}-{{ $root.Values.app.name }}"
namespace: {{ $root.Release.Namespace | quote }}
subjects:
- kind: ServiceAccount
name: {{ $root.Values.app.name }}
apiGroup: ""
roleRef:
kind: ClsuterRole
name: {{ . }}
apiGroup: rbac.authorization.k8s.io
{{- end }}
{{- end }}
여기서는 3가지 구역으로 나뉘어지는데 첫 번째에는 autoCreate로 생성하는 Role을 바인딩하는 RoleBinding을 생성한다. RoleBinding은 하나의 Role만 바인딩 할 수 있기 때문에 사용할 Role 개수만큼 RoleBinding을 생성해줘야 한다. 모든 RoleBinding은 차트에서 생성한 서비스어카운트에 Role을 바인딩한다.
두 번째 구역에서는 기존에 있는 Role을 바인딩하는 RoleBinding을 생성한다. 바인딩 하고 싶은 기존의 Role들을 복수개를 받을 수 있도록 roleBinding.additionalRoles
는 배열로 받는다고 가정하자. 그리고 여기서는 range 문을 사용하여 템플릿 배열 변수에 대해 루프를 돌릴 수 있다. 그래서 range 문을 사용하여 각 role마다 RoleBinding을 만들어 낼 수 있다. range 내부에서 요소에 접근하려면 .
을 사용하면 된다.
그런데 여기서 주의할 점은 range 문을 사용하면 현재 스코프가 배열의 요소로 변경되기 때문에 range문 내부에서는 .Values
나 .Release
변수에 접근이 기본적으로 불가능하다. 물론 아래의 간단한 트릭을 써서 .Values
나 .Release
변수에 접근할 수 있는데 range나 with문 내부에서 변수에 접근하기 위해 파일 맨 윗 부분에 이 값을 넣고, range나 with문 내부에서는 $root.Values
나 $root.Release
를 통해 해당 변수에 접근할 수 있다.
{{- $root := . -}}
마지막으로 RoleBinding에서는 ClusterRole도 사용할 수 있기 때문에 rbac.roleBinding.additionalClusterRoles
이라는 변수를 추가하여 ClusterRole도 바인딩시킬 수 있다.
clusterRoleBinding.yaml
RoleBinding과 내용은 비슷하기 때문에 설명은 생략한다.
{{- $root := . -}}
{{- if .Values.rbac.enabled }}
{{- range $role := .Values.rbac.clusterRoleBinding.clusterRoles }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: "{{ . }}-{{ $root.Values.app.name }}"
subjects:
- kind: ServiceAccount
name: {{ $root.Values.app.name }}
apiGroup: ""
roleRef:
kind: ClusterRole
name: {{ . }}
apiGroup: rbac.authorization.k8s.io
---
{{- end }}
{{- end }}
values.yaml
RBAC도 기본적으로는 사용하지 않기 때문에 rbac.enabled
는 false로 한다. 만약에 사용하는 경우라면 보통 Spring Cloud Kubernetes를 사용하는 애플리케이션으로 간주하고 Role 자동 생성을 지원하면 편리할 것이다. 그래서 rbac.roleBinding.autoCreate
는 true로 설정하자. 나머지 변수들은 제로값으로 설정하자.
app:
name: todo-api
replicas: 1
image: beer1/todo-server-kotlin:0.1.0
port: 9000
customCommand: []
customArgs: []
env: []
resources: {}
podAnnotations: {}
podLabels: {}
probes:
readiness: {}
liveness: {}
rbac:
enabled: false
service:
enabled: true
port: 80
ingress:
enabled: false
className: nginx
host: ""
path: ""
pathType: Prefix
rbac:
enabled: false
roleBinding:
autoCreate: true
additionalRoles: []
additionalClusterRoles: []
clusterRoleBinding:
clusterRoles: []
configmap.yaml & secret.yaml
Spring Cloud Kuberenetes에서 컨피그맵, 시크릿을 통해 프로퍼티를 불러온다면 ConfigMap, secret도 차트에 넣으면 편리할 것이다.
{{- $root := . -}}
{{- with .Values.applicationYamlConfig }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .name }}
namespace: {{ $root.Release.Namespace | quote }}
data:
application.yaml: |
{{ .value | nindent 4 }}
{{- end }}
{{- $root := . -}}
{{- with .Values.applicationYamlSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ .name }}
namespace: {{ $root.Release.Namespace | quote }}
stringData:
application.yaml: |
{{ .value | nindent 4 }}
{{- end }}
- 사실 git에 values.yaml을 저장하기 때문에 시크릿을 차트에 바로 넣는 것은 권장하는 방식은 아니다.
최종 values.yaml
선택적으로 사용하기 때문에 applicationYamlConfig
, applicationYamlSecret
기본값은 빈값이다. 그리고 사용하는 경우 name과 value는 필수이며 value는 |
로 multiString 구조가 되어야 한다. 이를 암시하기 위해서 주석처리로 예시 값을 넣어주면 조금 더 좋다.
최종적으로 values.yaml은 다음과 같이 설정하였다.
app:
name: todo-api
replicas: 1
image: beer1/todo-server-kotlin:0.1.0
port: 9000
customCommand: []
customArgs: []
env: []
resources: {}
podAnnotations: {}
podLabels: {}
probes:
readiness: {}
liveness: {}
rbac:
enabled: false
service:
enabled: true
port: 80
ingress:
enabled: false
className: nginx
host: ""
path: ""
pathType: Prefix
rbac:
enabled: false
roleBinding:
autoCreate: true
additionalRoles: []
additionalClusterRoles: []
clusterRoleBinding:
clusterRoles: []
applicationYamlConfig: {}
# name: configmap
# value: |
applicationYamlSecret: {}
# name: secret
# value: |
실제로 배포해보기
실제로 애플리케이션을 배포해보자. 예시 애플리케이션은 Spring Cloud Kubernetes를 사용한다고 가정하고 Service는 붙이지만 Ingress는 붙이지 않는다고 가정하자. values.yaml을 아래와 같이 설정하고 배포해보자.
todo-api.yaml
app:
name: todo-api
replicas: 1
image: beer1/todo-server-kotlin:0.1.0
port: 9000
env:
- name: SPRING_PROFILES_ACTIVE
value: kubernetes
resources:
requests:
cpu: 300m
memory: 500Mi
limits:
cpu: 300m
memory: 500Mi
podLabels:
tier: backend
probes:
readiness:
httpGet:
path: /actuator/health/readiness
port: 9000
initialDelaySeconds: 10
periodSeconds: 10
liveness:
httpGet:
path: /actuator/health/liveness
port: 9000
initialDelaySeconds: 120
periodSeconds: 10
service:
enabled: true
rbac:
enabled: true
roleBinding:
autoCreate: true
applicationYamlConfig:
name: todo-api
value: |
spring:
datasource:
url: jdbc:mysql://mysql.middleware:3306/todolist?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimezone=UTC
username: todouser
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
generate-ddl: false
database: mysql
database-platform: org.hibernate.dialect.MySQLDialect
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
cors:
origins: "*"
maxAge: 3600
test:
data: configmap
applicationYamlSecret:
name: todo-api
value: |
spring:
datasource:
password: toboBeer1!
test:
data: secret
helm install 명령어로 배포해보자.
$ helm install todo-api ./spring-app -n todo -f todo-api.yaml
NAME: todo-api
LAST DEPLOYED: Mon Sep 25 01:53:20 2023
NAMESPACE: todo
STATUS: deployed
REVISION: 1
TEST SUITE: None
그리고 리소스들이 잘 생성되었는지 확인해보자. 생성이 잘 된 것을 확인할 수 있다.
$ kubectl get deploy -n todo
NAME READY UP-TO-DATE AVAILABLE AGE
todo-api 1/1 1 1 9m38s
$ kubectl get svc -n todo
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
todo-api ClusterIP 10.105.208.173 <none> 80/TCP 9m50s
$ kubectl get role -n todo
NAME CREATED AT
spring-application 2023-09-24T16:53:21Z
$ kubectl get rolebinding -n todo
NAME ROLE AGE
spring-role-todo-api Role/spring-application 10m
$ kubectl get sa -n todo
NAME SECRETS AGE
default 0 2d2h
todo-api 0 10m
$ kubectl get cm -n todo
NAME DATA AGE
kube-root-ca.crt 1 2d2h
todo-api 1 10m
$ kubectl get secret -n todo
NAME TYPE DATA AGE
sh.helm.release.v1.todo-api.v1 helm.sh/release.v1 1 10m
sh.helm.release.v1.todo-api.v2 helm.sh/release.v1 1 7m15s
sh.helm.release.v1.todo-api.v3 helm.sh/release.v1 1 6m8s
sh.helm.release.v1.todo-api.v4 helm.sh/release.v1 1 3m32s
todo-api Opaque 1 10m
'DevOps > Kubernetes' 카테고리의 다른 글
Helm Chart Repository 만들기 (1) - Github (0) | 2023.10.09 |
---|---|
Helm Chart 유효성 검증과 문서화 (0) | 2023.10.03 |
Helm Chart 소개 (0) | 2023.09.17 |
Encryption at Rest (쿠버네티스 데이터 암호화) (0) | 2023.08.27 |
[11] 쿠버네티스 ConfigMap & Secret (0) | 2023.08.27 |
댓글