본문 바로가기
DevOps/Kubernetes

Helm Chart 만들어보기

by 비어원 2023. 9. 25.
728x90

이번에는 실제로 마이크로서비스들을 배포하기 위한 helm chart를 직접 만들어보도록 하자. Chart를 만들기 전에, 실제로 어떤 목적으로 패키징할지 고민해봐야 한다. 이번 실습에서는 한국에서 자주 사용하는 Spring Boot 기반 애플리케이션을 배포하기 위한 helm chart를 만들어보자.

 

실습에 대한 파일들은 Github에 올려두었습니다.

https://github.com/beer-one/charts

 

GitHub - beer-one/charts

Contribute to beer-one/charts development by creating an account on GitHub.

github.com

 

차트 이름 결정하기

먼저 차트 이름을 결정해야 한다. 일단 차트를 만들 디렉터리를 하나 생성하고, 그 하위에 사용할 차트 이름과 같은 이름으로 디렉터리를 하나 더 생성하자. 그리고 그 하위에 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는 차트를 개발한 사람의 정보를 적어두는 곳이다. (필수는 아님)

 

여기서 versionappVersion에 대해 헷갈릴 수도 있는데 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

 

Spring Cloud Kubernetes [1] 소개 및 PropertySource

Spring Cloud 프로젝트에서는 클라우드 기반 분산 시스템에서 필요로하는 일반적은 패턴들을 빠르게 구축할 수 있도록 도구를 제공해준다. 대표적으로는 다음의 기능을 제공한다. (언급된 것 외에

beer1.tistory.com

 

 

각 쿠버네티스 리소스 템플릿화 하기

이제 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 변수에 설정할 수 있다. 여기서 withtoYaml은 뒤에서 따로 다루겠다.

 

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.portapp.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
728x90

댓글