Kaggle 을 통해서 Machine Learning 을 배우고 Kaggle Competition 에 참여하는 방법을 알 수 있습니다.
https://www.youtube.com/playlist?list=PL98nY_tJQXZnP-k3qCDd1hljVSciDV9_N
Kaggle 을 통해서 Machine Learning 을 배우고 Kaggle Competition 에 참여하는 방법을 알 수 있습니다.
https://www.youtube.com/playlist?list=PL98nY_tJQXZnP-k3qCDd1hljVSciDV9_N
Helm chart 의 Life cycle 을 이해하면 조금 더 고급스러운 chart 를 만들 수 있다. 예를 들면 이전 chart 의 애플리케이션에서 postgresql DB를 사용하기 때문에 사용자, 데이터베이스, 테이블을 생성해야 하는 경우를 생각해 보자. 차트가 배포될 때 애플리케이션이 실행되기 이전에 해당 작업들을 할 수 있다면 chart 배포 한 번으로 모든 배포를 끝낼 수 있으니 멋진 일이다.
Helm 에서는 chart 가 실행되기 이전에 혹은, 실행된 이후에 작업을 정의할 수 있도록 hook 기능을 제공한다. 이러한 hook 의 종류는 아래와 같다.
Annotation 값 | 설 명 |
---|---|
pre-install |
Executes after templates are rendered, but before any resources are created in Kubernetes |
post-install |
Executes after all resources are loaded into Kubernetes |
pre-delete |
Executes on a deletion request before any resources are deleted from Kubernetes |
post-delete |
Executes on a deletion request after all of the release's resources have been deleted |
pre-upgrade |
Executes on an upgrade request after templates are rendered, but before any resources are updated |
post-upgrade |
Executes on an upgrade request after all resources have been upgraded |
pre-rollback |
Executes on a rollback request after templates are rendered, but before any resources are rolled back |
post-rollback |
Executes on a rollback request after all resources have been modified |
test |
Executes when the Helm test subcommand is invoked ( view test docs) |
hook 을 정의하는 것은 Annotation 으로 하며, 앞에서 설명한 시나리오의 경우에는 pre-install hook 을 사용하는 것이 적절하기 때문에 "helm.sh/hook": pre-install 로 설정한다. 또한 이런 단발성 호출의 경우에는 Job 으로 설정하는 것이 좋다.
apiVersion: batch/v1
kind: Job
metadata:
...
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-weight": "1"
"helm.sh/hook-delete-policy": before-hook-creation
hook-weight 는 낮을 수록 우선순위가 높으며, hook-delete-policy 의 before-hook-creation 은 hook 으로 실행된 리소스를 다음 hook 이 실행될 때 까지 지우지 않고 남겨둔다는 의미이다. policy 는 아래와 같이 3가지가 있다.
Annotation Value | Description |
---|---|
before-hook-creation |
Delete the previous resource before a new hook is launched (default) |
hook-succeeded |
Delete the resource after the hook is successfully executed |
hook-failed |
Delete the resource if the hook failed during execution |
그럼 postgresql 을 설치하고 사용자, 데이터베이스, 테이블을 생성하는 pre-install hook 을 작성해 보자.
bitnami chart repo 에서 postgresql 을 다운받아 설치한다.
$ helm repo add bitnami https://charts.bitnami.com/bitnami
$ helm repo update
$ helm upgrade -i postgresql bitnami/postgresql --version 10.8.0 -n decapod-db --create-namespace \
--set postgresqlPassword=password \
--set persistence.enabled=true \
--set persistence.storageClass=rbd \
--set persistence.size=10Gi
chart 의 value 값을 필요한 부분만 override 하였다. db user 는 postgres 이며 db password는 password 로 설치했다. 테스트 환경에서는 외부 스토리지를 제공하고 있어 10Gi 로 설정하였다.
$ kubectl get pods -n decapod-db
NAME READY STATUS RESTARTS AGE
postgresql-postgresql-0 1/1 Running 0 35h
$ kubectl get svc -n decapod-db
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
postgresql ClusterIP 10.233.46.14 <none> 5432/TCP 35h
postgresql-headless ClusterIP None <none> 5432/TCP 35h
chart 가 설치될 때 한 번 실행되면 되므로 Job 타입으로 리소스를 작성한다. psql 명령을 사용할 수 있는 컨테이너 이미지를 활용하고 수행할 명령어들은 쉘 스크립트로 작성하였다.
{chart_source_home}/templates/pre-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "tks-contract.fullname" . }}
namespace: {{ .Values.namespace }}
labels:
{{- include "tks-contract.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
metadata:
name: {{ include "tks-contract.fullname" . }}
spec:
restartPolicy: Never
containers:
- name: pre-install-job
image: "bitnami/postgresql:11.12.0-debian-10-r44"
env:
- name: DB_ADMIN_USER
value: {{ .Values.db.adminUser }}
- name: PGPASSWORD
value: {{ .Values.db.adminPassword }}
- name: DB_NAME
value: {{ .Values.db.dbName }}
- name: DB_USER
value: {{ .Values.args.dbUser }}
- name: DB_PASSWORD
value: {{ .Values.args.dbPassword }}
- name: DB_URL
value: {{ .Values.args.dbUrl }}
- name: DB_PORT
value: "{{ .Values.args.dbPort }}"
command:
- /bin/bash
- -c
- -x
- |
# check if ${DB_NAME} database already exists.
psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -lqt | cut -d \| -f 1 | grep -qw ${DB_NAME}
if [[ $? -ne 0 ]]; then
psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -c "CREATE DATABASE ${DB_NAME};"
fi
# check if ${DB_USER} user already exists.
psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -tc '\du' | cut -d \| -f 1 | grep -qw ${DB_USER}
if [[ $? -ne 0 ]]; then
psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -c "create user ${DB_USER} SUPERUSER password '${DB_PASSWORD}';"
fi
# check if contracts table in tks database already exists.
psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -d ${DB_NAME} -tc '\dt' | cut -d \| -f 2 | grep -qw contracts
if [[ $? -ne 0 ]]; then
echo """
\c ${DB_NAME};
CREATE TABLE contracts
(
contractor_name character varying(50) COLLATE pg_catalog."default",
id uuid primary key,
available_services character varying(50)[] COLLATE pg_catalog."default",
updated_at timestamp with time zone,
created_at timestamp with time zone
);
CREATE UNIQUE INDEX idx_contractor_name ON contracts(contractor_name);
ALTER TABLE contracts CLUSTER ON idx_contractor_name;
INSERT INTO contracts(
contractor_name, id, available_services, updated_at, created_at)
VALUES ('tester', 'edcaa975-dde4-4c4d-94f7-36bc38fe7064', ARRAY['lma'], '2021-05-01'::timestamp, '2021-05-01'::timestamp);
CREATE TABLE resource_quota
(
id uuid primary key,
cpu bigint,
memory bigint,
block bigint,
block_ssd bigint,
fs bigint,
fs_ssd bigint,
contract_id uuid,
updated_at timestamp with time zone,
created_at timestamp with time zone
);
""" | psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER}
fi
{chart_source_home}/values.yaml
args:
port: 9110
dbUrl: postgresql.decapod-db.svc
dbPort: 5432
dbUser: tksuser
dbPassword: password
db:
adminUser: postgres
adminPassword: password
dbName: tks
컨테이너에서 필요한 값들(db superuser admin 과 password, db url, db port 등)은 env 로 전달한다. 참고로 postgresql 은 psql 로 접속할 때 패스워드를 전달하는 아규먼트가 없다. 대신 환경 변수로 다음과 같이 설정하면 패스워드를 사용하여 접속할 수 있다. ($ export PGPASSWORD=password)
chart 를 배포하면 다음과 같이 job 이 실행됨을 알 수 있다.
$ kubectl get pods -n tks
NAME READY STATUS RESTARTS AGE
tks-contract-h5468 0/1 Completed 0 31h
$ kubectl logs tks-contract-h5468 -n tks
+ psql -h postgresql.decapod-db.svc -p 5432 -U postgres -lqt
+ cut -d '|' -f 1
+ grep -qw tks
+ [[ 0 -ne 0 ]]
+ psql -h postgresql.decapod-db.svc -p 5432 -U postgres -tc '\du'
+ cut -d '|' -f 1
+ grep -qw tksuser
+ [[ 0 -ne 0 ]]
+ psql -h postgresql.decapod-db.svc -p 5432 -U postgres -d tks -tc '\dt'
+ cut -d '|' -f 2
+ grep -qw contracts
+ [[ 0 -ne 0 ]]
쿠버네티스에 서비스를 배포하기 위해 사용하는 대표적인 방법중에 하나가 바로 Helm chart 이다. 한마디로 말해서 Helm chart 는 쿠버네티스 용도의 패키징된 s/w 라 할 수 있다. Helm chart 문법은 go template 을 활용하였기 때문에 go template 을 안다면 조금 더 쉽게 이해할 수 있다.
여기서 설명하는 내용은 사전에 쿠버네티스 클러스터가 있으며 kubectl 로 api 에 접근할 수 있는 환경이 있다는 것을 가정한다.
그럼 도커 이미지를 가지고 쿠버네티스에 Helm 으로 배포할 수 있는 Helm chart 를 만들어 보자.
먼저 Helm (v3)을 설치한다.
$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
$ helm version
version.BuildInfo{Version:"v3.4.0", GitCommit:"7090a89efc8a18f3d8178bf47d2462450349a004", GitTreeState:"clean", GoVersion:"go1.14.10"}
Helm 버전은 틀릴 수 있는데 v.3.0.0 이상이면 상관없다.
아무 디렉토리로 이동해서 아래의 명령어로 기본 코드를 생성한다. (여기서 예제는 gRpc 기반의 tks-contract 이라는 프로그램을 기반으로 했다)
$ helm create tks-contract
helm create 명령어는 tks-contract 디렉토리를 만들고 아래와 같은 구조로 디렉토리와 샘플 코드를 자동으로 만들어 준다. 그 구조는 다음과 같다.
$ tree tks-contract
tks-contract
├── Chart.yaml
├── charts
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
3 directories, 10 files
Chart.yaml 은 chart 에 대한 기본적인 정보가 있는 파일이다. chart 명, chart 버전, chart 설명 등을 적을 수 있으며, 지금은 별로 바꿀 내용이 없다.
charts 디렉토리는 의존성을 관리한다. 예를 들면 DB를 이용하는 웹 애플리케이션 chart 를 만들 때 DB 가 설치되어야 한다면 여기에 chart 를 넣거다 chart repo 에 존재하는 chart 의 링크를 기술 할 수 있다. 이 것도 지금은 바꿀 내용이 없다.
templates 디렉토리는 가장 중요한 디렉토리다. 이 디렉토리 안에는 쿠버네티스 리소스 yaml 파일들이 (예를 들면, Deployment, Service 와 같은 리소스 정의 파일) 위치한다.
values.yaml 파일은 한마디로 변수들을 정의한 파일이다. templates 디렉토리 안에 있는 yaml 파일들의 특정 변수 값을 치환하고 싶을 때 값을 선언해 놓는 곳이다. 일반적으로 key: value 형태로 기술한다.
먼저 간단한 templates 디렉토리 안의 service.yaml 을 변경해 보자.
apiVersion: v1
kind: Service
metadata:
name: {{ include "tks-contract.fullname" . }}
namespace: {{ .Values.namespace }}
labels:
{{- include "tks-contract.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.args.port }}
protocol: TCP
selector:
{{- include "tks-contract.selectorLabels" . | nindent 4 }}
go 템플릿을 사용하기 때문에 {{ }} 기호로 값을 치환한다는 것을 알 수 있다. 일반적으로 values.yaml 파일에 정의된 값을 치환할 수 있게 적혀있다. 가령 {{ .Values.namespace }} 는 values.yaml 파일의 namespace 를 키로 하는 값을 가져와서 치환하라는 의미이다. 실제 values.yaml 에는 아래와 같이 되어 있다.
namespace: tks
service:
type: LoadBalancer
port: 9110
values.yaml 파일의 service 아래의 type 을 키로 하는 값, 즉 LoadBalancer 라는 값을 가져오기 위해서는 {{ .Values.service.type }} 으로 적어주면 된다.
마지막 라인의 {{- include "tks-contract.selectorLabels" . | nindent 4 }} 와 같이 include 는 정의된 템플릿 호출을 의미한다. 즉 tks-contract.selectorLabels 라는 정의된 템플릿을 호출했다고 할 수 있다. 템플릿 정의들은 보통 templates 디렉토리 아래 _helpers.tpl 파일에 정의하며 define 으로 선언되어 있다. _helpers.tpl 파일의 일부를 살펴보자.
{{/*
Selector labels
*/}}
{{- define "tks-contract.selectorLabels" -}}
app.kubernetes.io/service: tks
app.kubernetes.io/name: {{ include "tks-contract.name" . }}
{{- end }}
{{/* */}} 은 주석처리이다. 그 아래에 {{- define "tks-contract.selectorLabels" -}} ... {{- end }} 은 템플릿 정의이다.
{{- include "tks-contract.selectorLabels" . | nindent 4 }} 에서 {{- 는 맨앞에 쓰였을 때 {{ }} 코드가 차지하는 영역의 줄바꿈과 공백을 없애라는 뜻이며 . 은 values.yaml 에 있는 모든 변수들을 아규먼트로 넘긴다는 뜻이다. 마지막으로 | 는 shell 에서의 파이프라인과 동일하고 nindent 4 는 결과를 프린트 할 때 공백 4개를 멀티라인으로 계속 들여쓰라는 의미이다.
정리해 보면 service.yaml 과 _helper.tpl 파일을 이 활용되어 아래와 같이 작동된다는 것을 알 수 있다.
## service.yaml
selector:
{{- include "tks-contract.selectorLabels" . | nindent 4 }}
+
## _helpers.tpl
{{- define "tks-contract.selectorLabels" -}}
app.kubernetes.io/service: tks
app.kubernetes.io/name: {{ include "tks-contract.name" . }}
{{- end }}
================ 결과 ===============
selector:
app.kubernetes.io/service: tks
app.kubernetes.io/name: tks-contract
app.kubernetes.io/service: tks 와 app.kubernetes.io/name: tks-contract 가 들여쓰기 4 만큼 프린트 됐다. 물론 _helpers.tpl 내의 템플릿 정의를 찾아보면 {{ include "tks-contract.name" . }} 의 결과 값은 tks-contract 이다.
그리고 NOTES.txt 에 적힌 내용은 chart 가 배포되고 나서 터미널에 프린트된다.
코드를 원하는 값으로 수정하고 배포될 때의 최종 yaml 이 어떻게 될지 확인해보자.
먼저 Chart.yaml 이다.
$ vi tks-contract/Chart.yaml
apiVersion: v2
name: tks-contract
description: A Helm chart for tks-contract
type: application
version: 0.1.0
appVersion: 0.1.0
_helpers.tpl 은 selectorLabels 를 원하는 값을 넣기 위해서 49 라인만 수정하였다.
$ vi tks-contract/_helpers.tpl
...
{{/*
Selector labels
*/}}
{{- define "tks-contract.selectorLabels" -}}
app.kubernetes.io/service: tks
app.kubernetes.io/name: {{ include "tks-contract.name" . }}
{{- end }}
...
deployment.yaml 은 container 의 args 값을 추가했다.
$ vi tks-contract/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "tks-contract.fullname" . }}
namespace: {{ .Values.namespace }}
labels:
{{- include "tks-contract.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "tks-contract.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "tks-contract.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "tks-contract.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: tks-contract
containerPort: {{ .Values.args.port }}
protocol: TCP
command:
- /app/server
args: [
"-port", "{{ .Values.args.port }}",
"-dbhost", "{{ .Values.args.dbUrl }}",
"-dbport", "{{ .Values.args.dbPort }}",
"-dbuser", "{{ .Values.args.dbUser }}",
"-dbpassword", "{{ .Values.args.dbPassword }}",
"-info-address", "{{ .Values.args.tksInfoAddress }}",
"-info-port", "{{ .Values.args.tksInfoPort }}"
]
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
조건절은 {{- if }} 혹은 {{- if not }} {{ end }} 로 사용할 수 있고 {{- with 키 }} {{ end }} 는 with 절 안에서는 구조상 해당 키 아래의 값들을 사용하겠다는 의미이다. {{- toYaml 키 }} 는 키의 값을 그대로 Yaml 로 프린트 해 준다.
nodeSelector 를 예를 들어 보자.
## deployment.yaml
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
+
## values.yaml
nodeSelector:
taco-tks: enabled
============ 결과 =================
nodeSelector:
taco-tks: enabled
values.yaml 에 nodeSelector 키 아래에 taco-tks: enabled 라는 값을 그대로 toYaml 로 프린트 한다.
service.yaml 전체는 다음과 같다.
$ vi tks-contract/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "tks-contract.fullname" . }}
namespace: {{ .Values.namespace }}
labels:
{{- include "tks-contract.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.args.port }}
protocol: TCP
selector:
{{- include "tks-contract.selectorLabels" . | nindent 4 }}
values.yaml 에 전체 값을 넣는다.
$ vi tks-contract/values.yaml
replicaCount: 1
namespace: tks
image:
repository: docker.io/seungkyu/tks-contract
pullPolicy: Always
tag: "latests"
imagePullSecrets: []
nameOverride: "tks-contract"
fullnameOverride: "tks-contract"
serviceAccount:
create: true
annotations: {}
name: "tks-info"
args:
port: 9110
dbUrl: postgresql.decapod-db.svc
dbPort: 5432
dbUser: tksuser
dbpassword: tkspassword
tksInfoAddress: tks-info.tks.svc
tksInfoPort: 9110
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: LoadBalancer
port: 9110
ingress:
enabled: false
annotations: {}
hosts:
- host: chart-example.local
paths: []
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:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector:
taco-tks: enabled
tolerations: []
affinity: {}
마지막으로 배포후 프린트될 내용은 NOTES.txt 을 수정한다.
$ vi tks-contract/templates/NOTES.txt
TKS-Contract
{{- if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Values.namespace }} svc -w {{ include "tks-contract.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Values.namespace }} {{ include "tks-contract.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
gRPC Call => $SERVICE_IP:{{ .Values.service.port }}
{{- end }}
작성된 chart 는 아래 명령어로 최종 yaml 이 어떻게 변환되어 배포되는지 dry-run 으로 확인할 수 있다.
$ helm upgrade -i tks-contract ./tks-contract --dry-run --debug
---
# Source: tks-contract/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: tks-info
labels:
helm.sh/chart: tks-contract-0.1.0
app.kubernetes.io/service: tks
app.kubernetes.io/name: tks-contract
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
---
# Source: tks-contract/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: tks-contract
namespace: tks
labels:
helm.sh/chart: tks-contract-0.1.0
app.kubernetes.io/service: tks
app.kubernetes.io/name: tks-contract
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
type: LoadBalancer
ports:
- port: 9110
targetPort: 9110
protocol: TCP
selector:
app.kubernetes.io/service: tks
app.kubernetes.io/name: tks-contract
---
# Source: tks-contract/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: tks-contract
namespace: tks
labels:
helm.sh/chart: tks-contract-0.1.0
app.kubernetes.io/service: tks
app.kubernetes.io/name: tks-contract
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/service: tks
app.kubernetes.io/name: tks-contract
template:
metadata:
labels:
app.kubernetes.io/service: tks
app.kubernetes.io/name: tks-contract
spec:
serviceAccountName: tks-info
securityContext:
{}
containers:
- name: tks-contract
securityContext:
{}
image: "docker.io/seungkyu/tks-contract:latests"
imagePullPolicy: Always
ports:
- name: tks-contract
containerPort: 9110
protocol: TCP
command:
- /app/server
args: [
"-port", "9110",
"-dbhost", "postgresql.decapod-db.svc",
"-dbport", "5432",
"-dbuser", "tksuser",
"-dbpassword", "",
"-info-address", "tks-info.tks.svc",
"-info-port", "9110"
]
resources:
{}
nodeSelector:
taco-tks: enabled
NOTES:
TKS-Contract
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace tks svc -w tks-contract'
export SERVICE_IP=$(kubectl get svc --namespace tks tks-contract --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
gRPC Call => $SERVICE_IP:9110
이전 글은 Cluster API 가 어떤 것이고 어떻게 동작하는지를 알아봤다면 이번에는 AWS 에 Cluster API를 이용하여 Kubernetes 를 쉽게 설치하는 방법을 설명한다. 사실 AWS를 어느 정도 알고있어야 (특히 VPC, Subnet, Route Table, Nat Gateway, Internet Gateway 같은 네트워크) 해당 내용들을 이해하기가 쉽기 때문에 Cluster API 로 구현되는 최종 Kubernetes Cluster 구성에 대해서 먼저 설명하겠다.
특정 리전 (여기서는 서울)에 3개의 가용존(AZ1~3)에 Kubernetes Master 와 Node 가 설치되게 된다. 하나의 AZ에는 public subnet 과 private subnet 이 만들어지며 public subnet은 인터넷과 통하는 Internet Gateway 와 연결되어 있다. 네트워크 통신을 위해서는 Route Table 을 통해서 default gateway (0.0.0.0/0)를 설정해야 하는데 public subnet 은 Internet Gateway 를 private subnet 은 EIP 가 할당되어 있는 NAT gateway 를 설정한다.
AWS 에서는 Internet Gateway 와 연결되어 있는데 subnet 을 public subnet 으로 명명하며 public IP 가 할당되는 자원들은 모두 public subnet 에 생성한다. 그 외의 subnet 은 private subnet 이라 명명하는데, 이 때 외부 outbound 연결을 하기 위해서는 public subnet 에 NAT Gateway 를 만들어 활용한다. 보라색 점선이 Internet Gateway 와 NAT Gateway 로 default gateway 를 설정한 내용을 보여준다.
요약하면 AWS 에서의 네트워크는 Subnet, Route table, Gateway (Internet 혹은 NAT) 로 구성된다.
모든 VM (Control plane과 Node) instance는 private subnet에 생성되어 외부에서 직접 VM 으로 접속하는 경로를 차단한다. 필요에 따라 외부의 Inbound 를 트래픽을 가능하게 하려면 Load Balancer 를 통해서 연결한다(물론 Internal LB도 가능하다). Load Balancer 가 private subent 의 VM 에 연결되기 위해서는 public subnet 이 LB에 등록되어야 한다. 다이어그램에서 이를 표현한 것이 초록색 점선이다. LB 와 Control plane 을 연결한 것은 API Server 가 LB 에 등록되어 로드밸랜싱된다는 의미이며, 고객 서비스의 연결은 Node VM 에 연결될 수 있다. 이 때 Service 리소스의 Type 을 LoadBalancer 로 지정하면 자동으로 연결된다.
이 기능들이 가능한 이유는 Kubernetes 에 Cloud provider controller 가 내장되어 있기 때문이다. 하지만 이 기능은 deprecated 될 예정으로 버전 1.23 부터는 소스가 분리될 예정이다.
또 한가지 일반적인 방법으로 설치하면 그림의 내용과 같이 모든 Node VM 이 AZ1 의 private subnet 에 몰려서 생성된다. 이는 가용성에 문제가 될 수 있으며 이를 해결하기 위해서는 아직은 experimental 버전인 MachinePool 기능을 사용해야 한다.
이제 본격적으로 Cluster API 로 Kubernetes Cluster 를 설치해 보자.
이전 글을 보면 Management cluster 에 Cluster API Controller 를 설치한 후 Custom Resource (CR) 를 생성하면 자동으로 Workload cluster 가 생성된다는 것을 설명하였다. 이를 잘 기억해 두고 Management cluster 는 존재한다는 가정하에 시작한다(인터넷에서 조회해보면 Kind 로 쉽게 Management cluster 를 구성하는 방법을 찾을 수 있다).
clusterctl 은 Managed cluster 에 Cluster API controller 를 설치하고 Workload cluster 를 위한 CR 생성을 도와주는 도구이다.
$ curl -L https://github.com/kubernetes-sigs/cluster-api/releases/download/v0.3.20/clusterctl-linux-amd64 -o clusterctl
$ chmod +x ./clusterctl
$ mv ./clusterctl /usr/local/bin/clusterctl
$ clusterctl version
=== output ===
clusterctl version: &version.Info{Major:"0", Minor:"3", GitVersion:"v0.3.20", GitCommit:"ea9dc4bdc2a9938325aab3817ece3e654873aaab", GitTreeState:"clean", BuildDate:"2021-06-30T22:10:51Z", GoVersion:"go1.13.15", Compiler:"gc", Platform:"linux/amd64"}
clusterawsadm 은 aws 에 필요한 Role 과 Policy 를 자동으로 생성해 주는 도구이다.
$ curl -L https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/download/v0.6.6/clusterawsadm-linux-amd64 -o clusterawsadm
$ chmod +x clusterawsadm
$ mv ./clusterawsadm /usr/local/bin/clusterawsadm
$ clusterawsadm version
=== output ===
clusterawsadm version: &version.Info{Major:"0", Minor:"6", GitVersion:"v0.6.6-4-d4593daa95fb96-dirty", GitCommit:"d4593daa95fb961be91dc6db869f26ca4359ebc0", GitTreeState:"dirty", BuildDate:"2021-06-01T20:05:33Z", GoVersion:"go1.13.15", AwsSdkVersion:"v1.36.26", Compiler:"gc", Platform:"linux/amd64"}
$ curl -L https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.2.13.zip -o awscliv2.zip
$ unzip awscliv2.zip
$ ./aws/install
$ aws --version
=== output ===
aws-cli/2.2.13 Python/3.8.8 Linux/4.4.0-87-generic exe/x86_64.ubuntu.16 prompt/off
$ mkdir -p ~/.aws
$ vi ~/.aws/credentials
[default]
aws_access_key_id=<< access_key_id >>
aws_secret_access_key=<< secret_access_key >>
$ vi ~/.aws/config
[default]
region = ap-northeast-2
clusterawsadm 을 활용하여 IAM Role 과 Policy 를 생성한다.
## 설정이 안되어 있을 때 환경 변수로 설정한다.
$ export AWS_REGION=ap-northeast-2
$ export AWS_ACCESS_KEY_ID=<< access_key_id >>
$ export AWS_SECRET_ACCESS_KEY=<< secret_access_key >>
$ clusterawsadm bootstrap iam create-cloudformation-stack
Workload cluster VM 에 접속할 key pair 를 Import 한다.
$ aws ec2 import-key-pair \
--key-name capi-seungkyu \
--public-key-material fileb://~/.ssh/id_rsa.pub
clusterctl 생성에 필요한 환경 변수를 다음과 같이 설정한다.
$ clusterawsadm bootstrap credentials encode-as-profile
=== output ===
<< crendentials >>
$ vi env.sh
export AWS_REGION=ap-northeast-2
export AWS_ACCESS_KEY_ID=<< access_key_id >>
export AWS_SECRET_ACCESS_KEY=<< secret_access_key >>
export AWS_B64ENCODED_CREDENTIALS=<< crendentials >>
$ source ./env.sh
clusterctl init 명령어를 사용하여 Management cluster 를 생성한다.
$ clusterctl init --core cluster-api:v0.3.20 --infrastructure aws:v0.6.6 --bootstrap kubeadm:v0.3.20 --control-plane kubeadm:v0.3.20 -v5
=== output ===
...
Fetching providers
Installing cert-manager
Waiting for cert-manager to be available...
...
Installing Provider="cluster-api" Version="v0.3.20" TargetNamespace="capi-system"
...
Installing Provider="bootstrap-kubeadm" Version="v0.3.20" TargetNamespace="capi-kubeadm-bootstrap-system"
...
Installing Provider="control-plane-kubeadm" Version="v0.3.20" TargetNamespace="capi-kubeadm-control-plane-system"
...
Installing Provider="infrastructure-aws" Version="v0.6.6" TargetNamespace="capa-system"
Your management cluster has been initialized successfully!
You can now create your first workload cluster by running the following:
clusterctl config cluster [name] --kubernetes-version [version] | kubectl apply -f -
필요한 환경변수를 설정하고 clusterctl config 명령어를 이용하여 CR 파일을 생성한다.
$ vi env-workload.sh
export AWS_CONTROL_PLANE_MACHINE_TYPE=t3.large
export AWS_NODE_MACHINE_TYPE=t3.large
export AWS_SSH_KEY_NAME=capi-seungkyu
$ source ./env-workload.sh
$ clusterctl config cluster capi-quickstart \
-n capi-quickstart \
--kubernetes-version v1.20.5 \
--control-plane-machine-count=3 \
--worker-machine-count=3 \
> capi-quickstart.yaml
capi-quickstart.yaml 을 확인하면 아래와 같다.
$ cat capi-quickstart.yaml
---
apiVersion: cluster.x-k8s.io/v1alpha3
kind: Cluster
metadata:
name: capi-quickstart
namespace: capi-quickstart
spec:
clusterNetwork:
pods:
cidrBlocks:
- 192.168.0.0/16
controlPlaneRef:
apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
kind: KubeadmControlPlane
name: capi-quickstart-control-plane
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
kind: AWSCluster
name: capi-quickstart
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
kind: AWSCluster
metadata:
name: capi-quickstart
namespace: capi-quickstart
spec:
region: ap-northeast-2
sshKeyName: capi-seungkyu
---
apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
kind: KubeadmControlPlane
metadata:
name: capi-quickstart-control-plane
namespace: capi-quickstart
spec:
infrastructureTemplate:
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
kind: AWSMachineTemplate
name: capi-quickstart-control-plane
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
extraArgs:
cloud-provider: aws
controllerManager:
extraArgs:
cloud-provider: aws
initConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: aws
name: '{{ ds.meta_data.local_hostname }}'
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: aws
name: '{{ ds.meta_data.local_hostname }}'
replicas: 3
version: v1.20.5
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
kind: AWSMachineTemplate
metadata:
name: capi-quickstart-control-plane
namespace: capi-quickstart
spec:
template:
spec:
iamInstanceProfile: control-plane.cluster-api-provider-aws.sigs.k8s.io
instanceType: t3.large
sshKeyName: capi-seungkyu
---
apiVersion: cluster.x-k8s.io/v1alpha3
kind: MachineDeployment
metadata:
name: capi-quickstart-md-0
namespace: capi-quickstart
spec:
clusterName: capi-quickstart
replicas: 3
selector:
matchLabels: null
template:
spec:
bootstrap:
configRef:
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
kind: KubeadmConfigTemplate
name: capi-quickstart-md-0
clusterName: capi-quickstart
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
kind: AWSMachineTemplate
name: capi-quickstart-md-0
version: v1.20.5
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
kind: AWSMachineTemplate
metadata:
name: capi-quickstart-md-0
namespace: capi-quickstart
spec:
template:
spec:
iamInstanceProfile: nodes.cluster-api-provider-aws.sigs.k8s.io
instanceType: t3.large
sshKeyName: capi-seungkyu
---
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
kind: KubeadmConfigTemplate
metadata:
name: capi-quickstart-md-0
namespace: capi-quickstart
spec:
template:
spec:
joinConfiguration:
nodeRegistration:
kubeletExtraArgs:
cloud-provider: aws
name: '{{ ds.meta_data.local_hostname }}'
해당 CR 은 아래 다이어그램 구조로 매칭된다.
Cluster API 는 공통이 Abstract Class 와 같이 기본을 정의해 놓았으며, Provider 는 실제 Cloud 에 맞는 구현을 정의하였다. AWSCluster 에는 VPC, subnet, Route table 등과 같은 인프라 생성 정보를 가지고 있고 AWSMachineTemplate 은 Kubernetes Control Plane 에 대한 정보를 AWSMachineTemplate 에는 Node 정보를 갖고 있다.
CR 을 배포하여 Workload cluster 를 생성한다.
$ kubectl create ns capi-quickstart
$ kubectl apply -f capi-quickstart.yaml
=== output ===
cluster.cluster.x-k8s.io/capi-quickstart created
awscluster.infrastructure.cluster.x-k8s.io/capi-quickstart created
kubeadmcontrolplane.controlplane.cluster.x-k8s.io/capi-quickstart-control-plane created
awsmachinetemplate.infrastructure.cluster.x-k8s.io/capi-quickstart-control-plane created
machinedeployment.cluster.x-k8s.io/capi-quickstart-md-0 created
awsmachinetemplate.infrastructure.cluster.x-k8s.io/capi-quickstart-md-0 created
kubeadmconfigtemplate.bootstrap.cluster.x-k8s.io/capi-quickstart-md-0 created
kubectl 명령어를 위해 아래와 같이 kubeconfig 를 생성한다.
$ clusterctl get kubeconfig capi-quickstart -n capi-quickstart > capi-quickstart.kubeconfig
한가지 중요한 부분은 Cluster API 는 Network 모듈이나, CSI 를 설치해 주지는 않는다. 이는 추가로 설치해야 한다.
$ kubectl --kubeconfig=./capi-quickstart.kubeconfig \
apply -f https://docs.projectcalico.org/v3.19/manifests/calico.yaml
네트워크 모듈까지 다 설치하면 정상적으로 Kubernetes Cluster 가 설치된 것을 볼 수 있다.
$ kubectl get kubeadmcontrolplane -n capi-quickstart
=== output ===
NAME INITIALIZED API SERVER AVAILABLE VERSION REPLICAS READY UPDATED UNAVAILABLE
capi-quickstart-control-plane true true v1.20.5 3 3 3
$ kubectl --kubeconfig=mycluster.kubeconfig get nodes
=== output ===
NAME STATUS ROLES AGE VERSION
ip-10-0-143-5.ap-northeast-2.compute.internal Ready control-plane,master 23h v1.20.5
ip-10-0-164-198.ap-northeast-2.compute.internal Ready <none> 23h v1.20.5
ip-10-0-222-84.ap-northeast-2.compute.internal Ready control-plane,master 23h v1.20.5
ip-10-0-255-19.ap-northeast-2.compute.internal Ready <none> 23h v1.20.5
ip-10-0-68-113.ap-northeast-2.compute.internal Ready <none> 23h v1.20.5
ip-10-0-80-79.ap-northeast-2.compute.internal Ready control-plane,master 23h v1.20.5
AWS 에서 Kubernetes 사용하는 방법은 Managed Service 인 EKS 서비스를 신청/설치 방법과 일반 VM 에 Kubernetes 를 설치하는 방법이 있다.
VM 에 Kubernetes 를 설치하는 방법은 VM 을 생성하고 필요한 Security Group 을 지정하고 Muti Control plane 에 ELB 를 연결하여 다중 Master 로 사용가능 하도록 세팅해야할 내용이 제법 있다. Kubernetes 자체 설치도 kubeadm 이나 kubespray 같은 툴을 사용해서 설치한다. - 역시 쉽지 않은 내용임에 분명하다.
지금 소개하는 방법은 Kubernetes Cluster Lifecycle SIG 에서 개발하고 있는 Cluster API 로 Operator Pattern 을 활용하여 Kubernetes 를 설치하는 방법이다. Cluster API 란 Management Kubernetes Cluster 에 Custom Controller 를 설치하고 Custom Resource 를 생성하면 자동으로 Cloud 에 VM 을 생성하고 Kubernetes 를 설치하는 방법이다. Controller 로 관리하기 때문에 VM 이 죽으면 다시 살려주는 장점이 있다. Kubernetes 가 설치된 VM 이 다운되었을 때 Self healing 으로 Recovering 해준다니 정말 멋진 아이디어인 것 같다.
Cluster API 의 시작은 2년 정도 전부터 시작되었는데 초장기에는 PoC 정도였다면 지금은 어느 정도 안정화 되어 사용하기에 문제가 없다.
개념을 보면 Management Kubernetes Cluster 를 설치(Kind, Minikube 등도 가능)하고 여기에 필요한 여러 Custom Controller 를 실행시킨다. 그리고 나서 Custom Resource 를 생성하면 지정된 Cloud 에 VM 을 생성하고 Workload Kubernetes Cluster (고객이 사용할 클러스터) 를 자동으로 생성해 주는 방법이다.
그림에서 보면 좌측 맨 위에 Cluster API Controller 가 있다. Cluster API Controller 는 마치 java class 로 치면 Abstraction Class 의 역활과 동일하다. Cluster Pod Network, Control Plane VM 갯수, Node VM 갯수 등을 관리한다.
이렇게 정의를 하면 Boostrap provider Controller 에서 kubeadm 으로 어떻게 Kubernetes 를 설치할 지를 관리한다.
Infrastructure provider Controller 에서는 AWS, Azure, GCP 와 같이 Public Cloud 의 자원 생성을 관리한다.
마지막으로 Control Plane provider Controller 특별하게 Control Plane 의 설치를 관리한다.
우측 위는 Custom Resource 를 보여주고 있다.
Cluster API Provider Controller 와 Infrastructure Provider Controller 의 상관 관계를 보면 다음과 같다.
Cluster API 는 Infrastructure Provider 에게 이런 식으로 구현을 해야 한다고 알려주는 가이드라고 보면 된다.
이제 세부적으로 하나씩 살펴보자.
Cluster API Controller 는 cluster 의 기본 정보를 관리하는 Controller 이다. 이 Controller 가 관리하는 Custom Resource 는 다음과 같이 4개가 있다.
이 중에서 machinehealthchecks 는 AWS Provider 에서는 사용하고 있지 않으므로 이를 제외하고 나머지 3가지만 살펴보겠다.
clusters CR(Custom Resource) 는 다음과 같은 정보를 가지고 있다.
Cluster Network:
Pods:
Cidr Blocks:
192.168.0.0/16
Control Plane Endpoint:
Host: capi-quickstart-apiserver-479433786.ap-northeast-2.elb.amazonaws.com
Port: 6443
Control Plane Ref:
API Version: controlplane.cluster.x-k8s.io/v1alpha3
Kind: KubeadmControlPlane
Name: capi-quickstart-control-plane
Namespace: capi-quickstart
Infrastructure Ref:
API Version: infrastructure.cluster.x-k8s.io/v1alpha3
Kind: AWSCluster
Name: capi-quickstart
Namespace: capi-quickstart
machinedeployments CR 의 정보는 Kubernetes Worker Node 에 대한 일반적인 내용이며 아래의 내용을 포함한다.
Cluster Name: capi-quickstart
Min Ready Seconds: 0
Progress Deadline Seconds: 600
Replicas: 3
Revision History Limit: 1
Selector:
Match Labels:
cluster.x-k8s.io/cluster-name: capi-quickstart
cluster.x-k8s.io/deployment-name: capi-quickstart-md-0
Strategy:
Rolling Update:
Max Surge: 1
Max Unavailable: 0
Type: RollingUpdate
Template:
Metadata:
Labels:
cluster.x-k8s.io/cluster-name: capi-quickstart
cluster.x-k8s.io/deployment-name: capi-quickstart-md-0
Spec:
Bootstrap:
Config Ref:
API Version: bootstrap.cluster.x-k8s.io/v1alpha3
Kind: KubeadmConfigTemplate
Name: capi-quickstart-md-0
Cluster Name: capi-quickstart
Infrastructure Ref:
API Version: infrastructure.cluster.x-k8s.io/v1alpha3
Kind: AWSMachineTemplate
Name: capi-quickstart-md-0
Version: v1.20.5
machines CR 은 Kubernetes Control Plane VM 별 정보와 Kubernetes Worker Node VM 별 정보를 갖고 있는데 둘다 아래의 정보를 동일하게 갖고 있다.
Bootstrap:
Config Ref:
API Version: bootstrap.cluster.x-k8s.io/v1alpha3
Kind: KubeadmConfig
Name: capi-quickstart-md-0-7f4xk
Namespace: capi-quickstart
UID: 7b095ef9-3380-45cc-950e-9bce8602f954
Data Secret Name: capi-quickstart-md-0-7f4xk
Cluster Name: capi-quickstart
Infrastructure Ref:
API Version: infrastructure.cluster.x-k8s.io/v1alpha3
Kind: AWSMachine
Name: capi-quickstart-md-0-dcdcx
Namespace: capi-quickstart
UID: 3383bd5b-1040-4577-a690-741c556d1076
Provider ID: aws:///ap-northeast-2a/i-090230ea1d12065ce
Version: v1.20.5
Bootstrap provider controller 는 Kubernetes 를 설치하는 kubeadm 관련 정보를 가지고 있다.
kubeadmconfigs 는 kubeadm 으로 kubernetes 를 설치할 때 사용하는 정보로서 Control Plane VM 별 정보와 Work Node VM별 정보를 갖고 있다.
Cluster Configuration:
API Server:
Extra Args:
Cloud - Provider: aws
Controller Manager:
Extra Args:
Cloud - Provider: aws
Dns:
Etcd:
Networking:
Scheduler:
Join Configuration:
Control Plane:
Local API Endpoint:
Advertise Address:
Bind Port: 0
Discovery:
Bootstrap Token:
API Server Endpoint: capi-quickstart-apiserver-479433786.ap-northeast-2.elb.amazonaws.com:6443
Ca Cert Hashes:
sha256:de8ef58efd55489531f681066eb77cec21a7d7f8e2a04eb41a06ad38b18d066e
Token: xxxx.xxxxxxxxxxx
Unsafe Skip CA Verification: false
Node Registration:
Kubelet Extra Args:
Cloud - Provider: aws
Name: {{ ds.meta_data.local_hostname }}
kubeadmconfigtemplates 는 Worker Node 가 join 한 template 정보로 Node 명을 가진다.
Template:
Spec:
Join Configuration:
Node Registration:
Kubelet Extra Args:
Cloud - Provider: aws
Name: {{ ds.meta_data.local_hostname }}
Infrastructure Provider Controller 는 각각의 Cloud 의 API 를 활용하여 리소스를 관리하는 Controller 로 aws 의 경우에는 아래의 정보를 가진다.
이 중에서 awsclusters, awsmachines, awsmachinetemplate 3가지만 살펴본다.
awsclusters 는 aws 에 설치된 Kubernetes workload cluster 에 대한 정보를 갖고 있다.
Bastion:
Allowed CIDR Blocks:
0.0.0.0/0
Enabled: false
Control Plane Endpoint:
Host: capi-quickstart-apiserver-479433786.ap-northeast-2.elb.amazonaws.com
Port: 6443
Identity Ref:
Kind: AWSClusterControllerIdentity
Name: default
Network Spec:
Cni:
Cni Ingress Rules:
Description: bgp (calico)
From Port: 179
Protocol: tcp
To Port: 179
Description: IP-in-IP (calico)
From Port: -1
Protocol: 4
To Port: 65535
Subnets:
Availability Zone: ap-northeast-2a
Cidr Block: 10.0.0.0/20
Id: subnet-04e972e6bfcb98336
Is Public: true
Nat Gateway Id: nat-0d456fe6c4dd2ce3d
Route Table Id: rtb-0dfccca96ffc00732
Tags:
Name: capi-quickstart-subnet-public-ap-northeast-2a
kubernetes.io/cluster/capi-quickstart: shared
kubernetes.io/role/elb: 1
sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart: owned
sigs.k8s.io/cluster-api-provider-aws/role: public
Availability Zone: ap-northeast-2a
Cidr Block: 10.0.64.0/18
Id: subnet-06f2be3eedb46d622
Is Public: false
Route Table Id: rtb-00090ad5d1de60b20
Tags:
Name: capi-quickstart-subnet-private-ap-northeast-2a
kubernetes.io/cluster/capi-quickstart: shared
kubernetes.io/role/internal-elb: 1
sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart: owned
sigs.k8s.io/cluster-api-provider-aws/role: private
Availability Zone: ap-northeast-2b
Cidr Block: 10.0.16.0/20
Id: subnet-02a9eed4c602fd3a2
Is Public: true
Nat Gateway Id: nat-050244cb7c9cff368
Route Table Id: rtb-020e9366a2da25625
Tags:
Name: capi-quickstart-subnet-public-ap-northeast-2b
kubernetes.io/cluster/capi-quickstart: shared
kubernetes.io/role/elb: 1
sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart: owned
sigs.k8s.io/cluster-api-provider-aws/role: public
Availability Zone: ap-northeast-2b
Cidr Block: 10.0.128.0/18
Id: subnet-03880f2f847b3c21e
Is Public: false
Route Table Id: rtb-06b52ab77da5c5f2c
Tags:
Name: capi-quickstart-subnet-private-ap-northeast-2b
kubernetes.io/cluster/capi-quickstart: shared
kubernetes.io/role/internal-elb: 1
sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart: owned
sigs.k8s.io/cluster-api-provider-aws/role: private
Availability Zone: ap-northeast-2c
Cidr Block: 10.0.32.0/20
Id: subnet-09ccabe6dd862df6a
Is Public: true
Nat Gateway Id: nat-030068539013d5c64
Route Table Id: rtb-0973a222fc772ff76
Tags:
Name: capi-quickstart-subnet-public-ap-northeast-2c
kubernetes.io/cluster/capi-quickstart: shared
kubernetes.io/role/elb: 1
sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart: owned
sigs.k8s.io/cluster-api-provider-aws/role: public
Availability Zone: ap-northeast-2c
Cidr Block: 10.0.192.0/18
Id: subnet-0be3353fd3b6f1637
Is Public: false
Route Table Id: rtb-0854effe818750d33
Tags:
Name: capi-quickstart-subnet-private-ap-northeast-2c
kubernetes.io/cluster/capi-quickstart: shared
kubernetes.io/role/internal-elb: 1
sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart: owned
sigs.k8s.io/cluster-api-provider-aws/role: private
Vpc:
Availability Zone Selection: Ordered
Availability Zone Usage Limit: 3
Cidr Block: 10.0.0.0/16
Id: vpc-070a545cb4b967dd7
Internet Gateway Id: igw-03a4ee0ca0f8d0f72
Tags:
Name: capi-quickstart-vpc
sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart: owned
sigs.k8s.io/cluster-api-provider-aws/role: common
Region: ap-northeast-2
Ssh Key Name: capi-seungkyu
awsmachinetemplates 은 aws vm spec 에 대한 정보를 template 으로 갖고 있다. template 은 control plane 과 worker node 각 1개씩 가진다.
control plane template
Template:
Spec:
Iam Instance Profile: control-plane.cluster-api-provider-aws.sigs.k8s.io
Instance Type: t3.large
Ssh Key Name: capi-seungkyu
worker node template
Template:
Spec:
Iam Instance Profile: nodes.cluster-api-provider-aws.sigs.k8s.io
Instance Type: t3.large
Ssh Key Name: capi-seungkyu
awsmachines 은 awsmachinetemplates 으로 연결된 control plane 과 worker node 별 aws VM 에 대한 정보를 갖는다.
contol plane vm1
Ami:
Cloud Init:
Secure Secrets Backend: secrets-manager
Iam Instance Profile: control-plane.cluster-api-provider-aws.sigs.k8s.io
Instance ID: i-0433d589b68e42cd0
Instance Type: t3.large
Provider ID: aws:///ap-northeast-2c/i-0433d589b68e42cd0
Ssh Key Name: capi-seungkyu
Control plane provider controller 는 Kubernetes control plane 에 대한 정보를 관리하는 Controller 이다.
kubeadmcontrolplanes 은 말그래도 control plane 의 정보를 담고 있으며 앞에서 살펴본 정보들을 활용한다.
Infrastructure Template:
API Version: infrastructure.cluster.x-k8s.io/v1alpha3
Kind: AWSMachineTemplate
Name: capi-quickstart-control-plane
Namespace: capi-quickstart
Kubeadm Config Spec:
Cluster Configuration:
API Server:
Extra Args:
Cloud - Provider: aws
Controller Manager:
Extra Args:
Cloud - Provider: aws
Dns:
Etcd:
Networking:
Scheduler:
Init Configuration:
Local API Endpoint:
Advertise Address:
Bind Port: 0
Node Registration:
Kubelet Extra Args:
Cloud - Provider: aws
Name: {{ ds.meta_data.local_hostname }}
Join Configuration:
Discovery:
Node Registration:
Kubelet Extra Args:
Cloud - Provider: aws
Name: {{ ds.meta_data.local_hostname }}
Replicas: 3
Rollout Strategy:
Rolling Update:
Max Surge: 1
Type: RollingUpdate
Version: v1.20.5
Argo Rollout 은 new version pods 와 old version pods 를 어떻게 구분하는 것일까? Rollout 리소스로 생성하면 ReplicaSet 의 형태인 Pod 템플릿 영역인 (spec.template
) 과 동일하다. 즉 ReplicaSet 이 Selector 로 지정한 label 로 Pod 를 찾아가는 것이다.
아래 Rollout 1개와 Service 2개를 가지는 sample yaml 파일을 배포하여 동작 방법을 살펴보자.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollout-bluegreen
spec:
replicas: 2
revisionHistoryLimit: 2
selector:
matchLabels:
app: rollout-bluegreen
template:
metadata:
labels:
app: rollout-bluegreen
spec:
containers:
- name: rollouts-demo
image: argoproj/rollouts-demo:blue
imagePullPolicy: Always
ports:
- containerPort: 8080
strategy:
blueGreen:
activeService: rollout-bluegreen-active
previewService: rollout-bluegreen-preview
autoPromotionEnabled: false
---
kind: Service
apiVersion: v1
metadata:
name: rollout-bluegreen-active
spec:
selector:
app: rollout-bluegreen
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
kind: Service
apiVersion: v1
metadata:
name: rollout-bluegreen-preview
spec:
selector:
app: rollout-bluegreen
ports:
- protocol: TCP
port: 80
Rollout 은 strategy 만 제외하면 ReplicaSet 혹은 Deployment 와 동일하다. 그래서 Rollout 을 배포하면 ReplicaSet 과 Pod 는 Kubernetes Core Resource 타입으로 동일하게 생성되고 조회할 수 있다.
.spec.selector.matchLabels 와 .spec.template.metadata.labels 에서 지정한 label 은 동일해야 한다. 이 부분은 ReplicaSet 에서 요구하는 spec 과 같다.
strategy 의 blueGreen 에는 rollout-bluegreen-active 와 rollout-bluegreen-preview 라는 service 가 2개가 지정되어 있다. Service 는 DNS 명 처럼 이름으로 IP 를 매칭하는 값을 가지는 기능과 Pod 를 연결하여 load balancing 해주는 대표적인 기능이 있다. rollout-bluegreen-active Service 에는 selector 로 "app: rollout-bluegreen" 값이 지정되어 있는데 이는 Rollout Template 에서 지정된 Pod 의 labels 를 가리킨다.
그런데 rollout-bluegreen-active Service 와 rollout-bluegreen-preview 2개 모두 같은 selector 로 같은 Pod 를 보고 있는데 어떻게 new version 과 old version 을 구분할까?
배포한 후에 설정들이 어떻게 바뀌는지 살펴보자.
$ kubectl apply -f rollout-blue-green.yaml
$ kubectl argo rollouts get rollout rollout-bluegreen --watch
rollout 명이 rollout-bluegreen-[해시코드값] 으로 되어 있는데 여기서 해시코드 값이 6565b74f44 은 ReplicaSet 의 해시코드와 동일하다.
$ kubectl get rs rollout-bluegreen
이 해시코드 값은 Service 에서도 찾아 볼 수 있다.
$ kubectl get svc rollout-bluegreen-active -o yaml
...
apiVersion: v1
kind: Service
metadata:
name: rollout-bluegreen-active
namespace: default
spec:
clusterIP: 10.233.59.133
clusterIPs:
- 10.233.59.133
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: rollout-bluegreen
rollouts-pod-template-hash: 6565b74f44
sessionAffinity: None
type: ClusterIP
...
selector 의 label 에 자동으로 rollouts-pod-template-hash: 6565b74f44 이 추가되어 rollout-bluegreen-active Service 는 현재 version 의 rollout 으로 생성된 Pod 를 가리키는 것을 알 수 있다.
이제 이미지를 업데이트 하여 upgrade 배포를 해보자.
$ kubectl argo rollouts set image rollout-bluegreen rollouts-demo=argoproj/rollouts-demo:yellow
$ kubectl argo rollouts get rollout rollout-bluegreen --watch
revision:2 아래에 rollout-bluegreen-6b5dc99488 이라는 새로운 해시코드 값으로 ReplicaSet 이 생성되었다.
rollout-bluegreen-preview Service 의 selector 로 지정된 labels 을 보면 rollouts-pod-template-hash:6b5dc99488 가 생성되어 new version 의 pod 를 가리키는 것을 알 수 있다.
$ kubectl get svc rollout-bluegreen-preview -o yaml
...
apiVersion: v1
kind: Service
metadata:
name: rollout-bluegreen-preview
namespace: default
spec:
clusterIP: 10.233.44.227
clusterIPs:
- 10.233.44.227
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: rollout-bluegreen
rollouts-pod-template-hash: 6b5dc99488
sessionAffinity: None
type: ClusterIP
...
배포를 완료하고 ReplicaSet 을 조회해 보면 이전 해시코드 값을 갖는 ReplicaSet 의 DESIRED, CURRENT, READY 가 0 으로 세팅되어 있는 것을 알 수 있다.
$ kubectl argo rollouts promote rollout-bluegreen
$ kubectl get rs -l app=rollout-bluegreen
NAME DESIRED CURRENT READY AGE
rollout-bluegreen-6565b74f44 0 0 0 5h35m
rollout-bluegreen-6b5dc99488 2 2 2 10m
이 방식을 응용하면 Deployment 로도 쉽게 배포 전략을 활용할 수 있다.
Argo rollout 은 Progressive Delivery 를 지원하는 툴이다.
아래의 그림에서 보듯이 Canary 배포를 진행할 때 일시적으로 배포를 홀딩한 상태에서 new version 에 대한 배포가 성공되었는지를 Metric 으로 판단하여 안전하게 배포를 완료할 수 있다. (metric 수집과 쿼리는 Prometheus 를 포함하여 다양한 모니터링 툴을 지원한다.)
Metric 을 통해 배포의 성공 여부를 판단하여 rollback 할 것인지, 계속 진행할 것인지를 결정할 수 있다는 것이 중요한 키 포인트다. 왜냐하면 쿠버네티스의 롤링 업데이트는 readiness 로 배포 성공여부를 판단하는데 이는 Metric 보다 판단하기에 부족할 수 있고, 중간에 에러가 발생하면 멈출 수 는 있지만 자동으로 rollback 되지는 않기 때문이다.
# kubectl create namespace argo-rollouts
# kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
# curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
# chmod +x ./kubectl-argo-rollouts-linux-amd64
# mv ./kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
# kubectl argo rollouts version
# cd ~/argo-rollout-demo
# curl -Lo basic-rollout-blue.yaml https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/rollout.yaml
# curl -Lo basic-service.yaml https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/service.yaml
# kubectl apply -f basic-rollout-blue.yaml
# kubectl apply -f basic-service.yaml
# kubectl patch svc rollouts-demo --patch \
'{"spec": { "type": "NodePort", "ports": [ { "nodePort": 31080, "port": 80, "protocol": "TCP", "targetPort": "http", "name": "http" } ] } }'
화면 접속
http://k2-master01:31080
콘솔 보기
# kubectl argo rollouts get rollout rollouts-demo --watch
# kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow
rollout 전략이 처음 step 은 20% 만 변경하는 것이기 때문에 5개의 replica 중에 1개만 old version(blue) 이고 4 개는 new version(yellow) 이다. 그리고 현재는 pause (일시멈춤) 상태로 더이상 rollout 배포가 진행되지 않고 있다.
# kubectl argo rollouts promote rollouts-demo
promote 로 계속 rollout 업그레이드를 진행하면 점차 new version 이 revision:2 영역인 canary 로 replica 개수를 증가시키는 것을 볼 수 있다. duration 을 10으로 주었기 때문에 10 초 단위로 20% 씩 자동으로 올려준다.
일정 시간이 다 지나면 전체가 전부 다 업그레이드 된다.
argo rollout 은 metric 으로 정의한 성공 여부에 따라 배포를 계속 진행할 것인지 아니면 abort 시킬 것인지를 자동으로 결정할 수 있다.
Kubernetes 기반의 monitoring 을 구현한다면 최우선 순위로 검토되는 오픈소스는 단연 Prometheus 이다. 최근에는 Kubernetes 위에 실행되는 Pod와 같은 자원을 효과적으로 관리하기 위해서 Operator 를 활용하고 있는 추세인데 이에 맞춰 Prometheus 에서도 Operator 를 지원하고 있다. 일반적으로 Operator 는 중심이 되는 프로그램, 즉 Pod 와 같이 CustomResource를 정의하면 자동으로 해당 프로그램을 띄우는 방식으로 사용된다. 하지만 Prometheus Operator 는 Prometheus 자체를 띄우고 관리하는 방식이 아니라 Prometheus 에서 사용되는 설정 등을 관리하기 위해서 사용된다. 한마디로 말하면 Prometheus Operator와 Prometheus 를 모두 설치하고, Operator 로 Prometheus 설정을 CustomResource 로 관리하는 방식이다.
그럼 이제 Prometheus Operator 와 Prometheus 를 설치해 보자. 둘다 모두 prometheus-community 에서 제공하는 helm chart 를 helm3 를 이용하여 설치한다.
helm repo 를 아래와 같이 등록한다.
$ helm repo add prometheus https://prometheus-community.github.io/helm-charts
$ helm repo update
$ helm search repo prometheus
많은 차트들이 검색되는데 그중에서 kube-prometheus-stack chart 로 Prometheus Operator 와 Prometheus 를 설치한다.
1. Prometheus Operator 설치
helm chart 에서 Oeveride 할 value 를 아래와 같이 지정한다.
# vi prometheus-operator-values.yaml
namespaceOverride: prometheus
fullnameOverride: prometheus-operator
defaultRules:
create: false
alertmanager:
enabled: false
grafana:
enabled: false
kubeApiServer:
enabled: false
kubelet:
enabled: false
kubeControllerManager:
enabled: false
coreDns:
enabled: false
kubeDns:
enabled: false
kubeEtcd:
enabled: false
kubeScheduler:
enabled: false
kubeProxy:
enabled: false
kubeStateMetrics:
enabled: false
nodeExporter:
enabled: false
prometheus:
enabled: false
prometheusOperator:
enabled: true
resources:
limits:
cpu: 2
memory: 2Gi
requests:
cpu: 1
memory: 1Gi
serviceMonitor:
selfMonitor: false
nodeSelector:
monitoring: enabled
createCustomResource: true
cleanupCustomResource: true
cleanupCustomResourceBeforeInstall: true
Prometheus Operator 를 설치할 namespace는 namespaceOverride 에 prometheus 로 지정한다.
Deployments 로 생성될 이름은 fullnameOverride 로 prometheus-operator 로 지정한다.
Operator 만 명시적으로 생성할 의도로 나머지 prometheus, exporter, alertmanager 등은 enabled: false 로 지정하여 생성하지 않는다.
Prometheus Operator 가 생성될 노드를 nodeSelector 로 지정할 수 있으며 여기서는 monitoring: enabled 로 세팅했으므로 Kubernetes node 에 해당 label 이 설정된 노드가 있어야 한다.
$ kubectl create ns prometheus
$ kubectl label namespace prometheus name=prometheus
$ kubectl label node k1-node01 monitoring=enabled
$ kubectl label node k1-node02 monitoring =enabled
$ kubectl label node k1-node03 monitoring =enabled
$ kubectl create secret generic etcd-client-cert -n prometheus \
--from-file=etcd-ca=/etc/ssl/etcd/ssl/ca.pem \
--from-file=etcd-client=/etc/ssl/etcd/ssl/member-k1-master01.pem \
--from-file=etcd-client-key=/etc/ssl/etcd/ssl/member-k1-master01-key.pem
prometheus namespace 에 label을 name=prometheus 로 준 이유는 나중에 CustomeResource 인 Service Monitor 가 prometheus namespace 안에 생성되면 자동으로 해당 설정을 적용하기 위해서 Operator 에 알려주는 정보이다. 별도로 설정하지 않아도 prometheus namesapce 에는 자동으로 설정된다.
또 한가지 알아야 할 것은 etcd metric 을 가져오기 위해서는 endpoint 뿐만이 아니라 mTLS 인증서도 있어야 한다. kubernetes 를 kubespary 로 설치하였다면 master 노드의 해당 위치에 etcd 인증서가 있으니 이를 가지고 secret 을 미리 생성해 놓아야 metric 을 가져올 수 있다.
이제 helm 명령어로 Prometheus Operator를 설치한다. upgrade -i 옵션을 주면 설치가 안되어 있을 경우에는 create 명령과 동일하고 설치가 되어 있을 경우에는 upgrade 명령과 동일하다.
$ helm upgrade -i prometheus-operator prometheus/kube-prometheus-stack --version 14.0.1 -f prometheus-operator-values.yaml -n prometheus
2. Prometheus 설치
동일한 kube-prometheus-stack 차트로 prometheus 를 설치한다.
# vi prometheus-values.yaml
defaultRules:
create: false
alertmanager:
enabled: true
service:
type: NodePort
nodePort: 30903
alertmanagerSpec:
nodeSelector:
monitoring: enabled
grafana:
enabled: false
kubeApiServer:
enabled: true
serviceMonitor:
namespaceSelector:
matchNames:
- default
kubeEtcd:
enabled: true
endpoints:
- 192.168.30.13
- 192.168.30.14
- 192.168.30.15
serviceMonitor:
scheme: https
insecureSkipVerify: false
serverName: localhost
caFile: /etc/prometheus/secrets/etcd-client-cert/etcd-ca
certFile: /etc/prometheus/secrets/etcd-client-cert/etcd-client
keyFile: /etc/prometheus/secrets/etcd-client-cert/etcd-client-key
kubeStateMetrics:
enabled: false
nodeExporter:
enabled: false
prometheusOperator:
enabled: false
serviceMonitor:
selfMonitor: true
prometheus:
enabled: true
service:
type: NodePort
nodePort: 30008
# Service for thanos service discovery on sidecar
# Enable this can make Thanos Query can use
# `--store=dnssrv+_grpc._tcp.${kube-prometheus-stack.fullname}-thanos-discovery.${namespace}.svc.cluster.local` to discovery
# Thanos sidecar on prometheus nodes
# (Please remember to change ${kube-prometheus-stack.fullname} and ${namespace}. Not just copy and paste!)
thanosService:
enabled: false
annotations: {}
labels: {}
portName: grpc
port: 10901
targetPort: "grpc"
thanosIngress:
enabled: false
type: NodePort
nodePort: 30901
prometheusSpec:
externalLabels:
cluster: k1
nodeSelector:
monitoring: enabled
secrets:
- etcd-client-cert
ruleNamespaceSelector:
matchLabels:
name: prometheus
ruleSelectorNilUsesHelmValues: false
serviceMonitorNamespaceSelector:
matchLabels:
name: prometheus
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorNamespaceSelector:
matchLabels:
name: prometheus
podMonitorSelectorNilUsesHelmValues: false
retention: 10d
replicas: 1
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: rbd
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
## ref: https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#thanosspec
thanos:
version: v0.18.0
kubeEtcd 는 etcd 가 master 3대에 설치되어 있어 endpoint에 해당 ip 를 모두 넣었으며 caFile, certFile, keyFile 은 앞서 설치한 secret 명을 경로로 포함하고 있다. alertmanager 와 prometheus 모두 nodePort 로 설치하여 접근할 수 있다.
serviceMonitorNamespaceSelector 와 podMonitorNamespaceSelector 를 지정하여 Monitor 할 대상을 CustomResource 를 통해 자동으로 설정할 수 있다. 앞서 prometheus namespace 를 생성할 때 label 을 name=prometheus 로 지정한 이유가 바로 이 부분이다.
마지막으로 Prometheus 가 사용할 storage class 는 rbd 로 지정하여 자동으로 생성되게 설정한다.
helm 으로 Prometheus 를 설치하는 명령은 다음과 같다.
$ helm upgrade -i prometheus prometheus/kube-prometheus-stack --version 14.0.1 -f prometheus-values.yaml -n prometheus