안녕하세요. IT 윈도우 킷도우입니다.
CKA 자격증 준비 4탄 쿠버네티스 클러스터에 이어서 계속해서 5탄 Scheduling에 대해서 알아보겠습니다.
본 내용은 CKA 강의로 유명한 Udemy CKA with practice tests의 내용을 토대로 정리한 것입니다.
Deployment Strategy
배포 전략에는 두가지가 있습니다.
1. Recreate 배포
배포된 것을 모두 파괴하고 새롭게 rollout 하는 것입니다. 이 방법을 사용하게 되면 중간에 Application Down이 발생할 수 있습니다.
2. Rolling Update 배포
먼저 새롭게 Replicaset을 만들고 파드를 선배포합니다. 그리고 기존의 Replicaset의 파드를 하나씩 삭제합니다. 배포 방법을 정의하지 않을 경우 롤링 배포로 default 진행됩니다.
명령어 정리
kubectl create -f deployment-definition.yaml #create
kubectl get deployments #select
kubectl apply -f deployment-definiition.yaml #create and deploy
kubectl set image deployment/myapp-deployment nginx=nginx:1.9.1 #deploy
kubectl rollout status deployment/myapp-deployment #select status
kubectl rollout history deployment/myapp-deployment #select history
kubectl rollout undo deployment/myapp-deployment #rollback
Commands and Argements
파드에 커맨드를 지정하는 방법은 아래와 같다.
apiVersion: v1
kind: Pod
metadata:
name: ubuntu-sleeper-pod
spec:
containers:
- name: ubuntu-sleeper
image: ubuntu-sleeper
command:["sleep2.0"] # ENTRYPOINT in docker image
args: ["10"] # CMD in docker image
# 위 정의는 아래와 같은 명령의 효과가 있습니다
# docker run --name ubuntu-sleeper --entrypoint sleep2.0 ubuntu-sleeper 10
# pod에 command를 정의할 땐 반드시 배열 형태로 정의해야합니다.
# ex. command: ["sleep","5000"]
# 또는
# command:
# -"sleep"
# -"5000"
# 또는 별도로 arge key를 사용해서 매개변수를 별도로 선언해도 됩니다.
# command: ["sleep"]
# arge: ["5000"]
Configure Environment Variable in Application
어플레케이션에 리눅스 환경 변수를 선언하는 법은 아래와 같습니다.
apiVersion: v1
kind: Pod
metadata:
name: simple-webapp-color
spec:
containers:
- name: simple-webapp-color
image: simple-webapp-color
ports:
- containerPort: 8080
env:
- name: APP_COLOR
value: pink
# in docker : docker run -e APP_COLOR=pink simple-webapp-color
이것은 plain 하게 환경 변수를 저장하는 방법입니다. 어플리케이션의 환경 변수를 지정하는 방법은 크게 3가지가 있습니다.
1. Plain Key Value
evn:
- name: APP_COLOR
value: pink
2. ConfigMap
evn:
- name: APP_COLOR
valueFrom:
configMapKeyRef
3. Secret
env:
- name: APP_COLOR
valueFrom:
secretKeyRef
Configuring ConfigMaps in Applications
ConfigMap 이 만들어진 목적은 아무래도 파드 단위로 환경 변수를 개별 선언, 저장하게 되면 관리하기가 매우 복잡해집니다. 이 환경 변수를 좀 더 중앙집중적으로 관리하기 위해 ConfigMap이 만들어진 것입니다.
Impertative한 방법으로 ConfigMap을 추가하는 방법은 아래와 같습니다.
kubectl create configmap <config-name> --from-literal=<key>=<value>
kubectl create configmap app-config --from-literal=APP_COLOR=blue
#여기서 환경 변수를 추가 하고 싶다면 --from-literal 명령을 계속 추가로 넣어주면 됩니다.
kubectl create configmap app-config --from-literal=APP_COLOR=blue --from-literal=APP_MOD=prod
하지만 이렇게 관리하는 것은 환견 변수가 많아지면 많아질 수록 관리하기 복잡해지므로 아래와 같이 파일을 통해서 선언합니다.
kubectl create configmap <config-name> --from-file=<path-to-file>
# ex) kubectl create configmap app-config --from-literal=app_config.prorperties
file은 아래와 같이 key value 형태로 저장합니다.
APP_COLOR: blue
APP_MODE: prod
이제 declarative 한 방법으로 ConfigMap을 만드는 방법을 알아보겠습니다. apiVersion, kind, metadata, data 이 큰 틀로 들어갑니다. pod와 다른점은 spec부분이 없습니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_COLOR: blue
APP_MODE: prod
# 이 이후 다음 명령어 수행하여 생성
# kubectl create -f config-map.yaml
이렇게 ConfigMap을 만들었다면, Pod에 이 ConfigMap 설정을 주입하면 됩니다.
apiVersion: v1
kind: Pod
metadata:
name: simple-webapp-color
spec:
containers
- name: simple-webapp-color
image: simple-webapp-color
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: app-config
생성한 configmap은 아래와 같이 조회할 수 있습니다.
kubectl get configmaps
kubectl descirbe configmaps
ConfigMap 데이터를 파드에 주입하는 다른 방법도 있습니다. Volumn을 활용합니다.
apiVersion: v1
kind: Pod
metdata:
name: simple-webapp-color
labels:
name: simple-webapp-color
spec:
containers:
- name: simple-webapp-color
image: simple-webapp-color
ports:
- containerPort: 8080
volumes:
- name: app-config-volume
configMap:
name: app-config
Configure Secrets in Applications
Secrets와 Configmaps의 차이는 Secrets이 좀 더 Safe하게 데이터를 저장한다. BaseMap으로 저장하는 것이 특징이다.
Imperative 한 방법으로 Secret을 만드는 법은 아래와 같습니다.
kubectl create secret generic --from-literal=<key>=<value>
# ex) kubectl create secret generic app-secret --from-literal=DB_Host=mysql
#여러 개 추가하고 싶다면 --from-literal=DB_User=root 이런식으로 여러 개 붙이면 된다.
#configmap과 마찬가지로 개별 설정이 너무 많으면 관리가 복잡해지니 이런경우 파일을 통해서 한번에 읽어오게 하자.
kubectl create secret generic <secret-name> --from-file=<path-to-file>
# ex) kubectl create secret generic app-secret --from-file=app_secret.prorperties
declarative 한 방법으로 Secret을 만드는 법은 아래와 같습니다.
apiVersion: v1
kind: Secret
metadata:
name: app-secret
data:
DB_Host: bXlxcWw= #(->mysql의 인코딩 데이터)
DB_User: cm9vdA== #(-> root의 인코딩 데이터)
DB_Password: cGFzd3JK #(->paswrd의 인코딩 데이터)
인코딩 데이터를 넣는 방법은 리눅스에서 echo -n 그리고 base64 명령어를 활용합니다.
echo -n 'mysql' | base64
secret을 조회하려면 아래와 같습니다.
kubectl get secrets
kubectl describe secrets
# 이렇게 조회할 경우 환경변수 값을 볼수가 없다.
# 따라서 아래와 같이 -o yaml 형태로 조회한다.
kubectl get secret app-secret -o yaml
여기서 base64로 인코딩된 값을 보려면 echo -n 그리고 base64 --decode 명령어를 활용하여 봅니다.
echo -n 'bXlzcWw=' | base64 --decode
다음으로 이 secret을 파드에 주입하는 방법은 configmap과 유사합니다.
apiVersion: v1
kind: Pod
metadata:
name: simple-webapp-color
spec:
containers:
- name: simple-webapp-color
image: simple-webapp-color
ports:
- containerPort: 8080
envFrom:
- secretRef:
name: app-secret
Single Value만 가져오는 경우는 아래와 같습니다.
apiVersion: v1
kind: Pod
metadata:
name: simple-webapp-color
spec:
containers:
- name: simple-webapp-color
image: simple-webapp-color
ports:
- containerPort: 8080
evn
- name: DB_Password
valueFrom:
secretKeyRef:
name: app-secret
key: DB_Password
Volume을 활용하여 가져오는 경우 아래와 같이 진행합니다.
apiVersion: v1
kind: Pod
metadata:
name: simple-webapp-color
spec:
containers:
- name: simple-webapp-color
image: simple-webapp-color
ports:
- containerPort: 8080
volumes:
- name: app-secret-volume
secret:
secretName: app-secret
Imperative 방식으로 Secret을 만드는 것과 declarative한 방법으로 만드는 것은 차이가 있습니다. Imperative 한 방식으로 Secret을 만들 경우 value값이 자동으로 base64로 인코딩되서 들어갑니다. 하지만 declarative한 방식으로 선언할 경우 우리가 직접 인코딩을 해줘야합니다. 따라서 시험을 치루르땐 secret은 Imperative한 방법으로 만드는 것이 좀 더 편리하다고 볼 수 있겠습니다.
More Detail Info about Secret
시크릿은 암호화되어 있지 않습니다. 단순 인코딩 돼있는 것으로 누구든 파일 열어 디코딩할 수 있습니다. 따라서 완벽히 안전한 데이터라고 볼 수 없다는 것을 명심하고 gitgub등에 파일을 push하면 안됩니다. 시크릿은 그 존재 자체만으로는 안전하지 않으면 그 주변 구성을 어떻게 하느냐에 따라서 Safe한지 Unsafe한지가 결졍된다고 보면 되겠습니다.
아무런 조치없이 단순히 Secret을 만들 경우 암호화되지 않은 상태로 ETCD 서버에 key-value가 쌓이게됩니다. ETCD 서버에는 Namespace에서 Pod를 만들 수 있다면 누구든 조회해볼 수 있는 공간입니다. 한 번 조회해 보겠습니다. k8s doc에서 Encrypting Secret Data at Rest라는 주제의 문서를 보면 ETCDCTL_API=3라는 것을 볼 수 있습니다. 관련해서 etctctl 명령어로 ETCD 서버에 질의를 할 수 있는데 이를 위해 아래와 같이 etcd-client를 다운로드합니다.
apt-get install etcd-client
secret을 보려면 인증서가 필요하고 아래와 같은 명령어로 시크릿을 조회해 볼 수 있습니다.
"ETCDCTL_API=3 etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/default/my-secret"
평문으로 된 데이터를 보려면 아래와 같이 조회합니다.
ETCDCTL_API=3 etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/default/my-secret | hexdump -C
데이터가 실제로 etcd에서 암호화되지 않은 포맷으로 저장된 것을 볼 수 있습니다. 바로 이 부분이 문제입니다. 암호화되지 않고 저장되는 중요 정보가 있다는 점입니다.
Encrypting Secret 관련 k8s 문서 가장 상단에 보면 해당 시크릿이 암호화가 돼있는지 안돼있는지를 미리 확인할 수 있는데 확인하는 방법은 아래와 같습니다.
ps -aux | grep kube-apiserver
# 또는 ls /etc/kubernetes/manifests 하위에서 kube-apiserver.yaml에서 아래 옵션이 들어있는지 확인
위와 같이 조회했을 때 --encryption-provider-config 옵션이 있는지를 살펴봅니다. 옵션이 없다면 암호화가 활성화 돼있지 않은 것입니다.
그러면 암호화를 적용하는 방법을 알아보겠습니다.
EncryptionConfiguration
Secret을 암호화하는데 필요한 첫번쨰는 EncryptionConfiguration을 구성하는 것입니다. 이를 만들어서 옵션으로 넘기면됩니다. k8s doc에서 EncryptionConfiguration 선언하는 것을 참조합니다.
#
# CAUTION: this is an example configuration.
# Do not use this for your own cluster!
#
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
- pandas.awesome.bears.example # a custom resource API
providers:
# This configuration does not provide data confidentiality. The first
# configured provider is specifying the "identity" mechanism, which
# stores resources as plain text.
# 여기서 암호화되는 순서가 중요하다. Identity:{}는 암호화를 하지 않겠다는 것이다. 첫번째가 이것이기 떄문에 암호화가 되지 않는 것이다. 데이터를 암호화하고 싶다면 aesgcm, aescbc, secretbox 중 하나가 가장 첫 번쨰에 있어야한다.
- identity: {} # plain text, in other words NO encryption
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
- resources:
- events
providers:
- identity: {} # do not encrypt Events even though *.* is specified below
- resources:
- '*.apps' # wildcard match requires Kubernetes 1.27 or later
providers:
- aescbc:
keys:
- name: key2
secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg==
- resources:
- '*.*' # wildcard match requires Kubernetes 1.27 or later
providers:
- aescbc:
keys:
- name: key3
secret: c2VjcmV0IGlzIHNlY3VyZSwgSSB0aGluaw==
아래는 예시입니다.
enc.yaml파일을 아래와 같이 만들어줍니다.
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <BASE 64 ENCODED SECRET>
- identity: {}
이렇게 만들고 위에서 얘기한대로 vi /etc/kubenetes/manifests/kube-apiserver.yaml 파일의 volume과 command 수정합니다. volumeMounts를 해줘야하고 command에 --encryption-provider-config=/etc/kubenetes/enc/enc.yaml을 추가하면 됩니다. k8s doc 문서를 참조합니다.
이 후 아래 명령어로 정사적으로 설정이 들어갔는 지 확인합니다.
ps aux | grep kube-api | grep encry
이제 또 다른 Secret을 만들고 이 것이 잘 암호화가 됐는지 확인해봅니다. 암호화 설정을 해줬기 때문에 암호화가 돼있어야 정상입니다.
ETCDCTL_API=3 etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/default/my-secret2 | hexdump -C
위 명령으로 조회하면 my-secret2가 조회가 안되는 것을 볼 수 있습니다. 물론 위 etctctl 명령을 수행한 이후 만든 secret에 한해 암호화가 되는 것도 잊지 말자.
댓글