디플로이먼트(Deployment) 는 파드와 레플리카셋(ReplicaSet)에 대한 선언적 업데이트를 제공한다. 또한 디플로이먼트는 쿠버네티스에서 상태가 없는(stateless)앱을 배포할 때 사용하는 가장 기본적인 컨트롤러이다.
디플로이먼트에서 의도하는 상태를 설명하고, 디플로이먼트 컨트롤러(Controller)는 현재 상태에서 의도하는 상태로 비율을 조정하며 변경한다. 새 레플리카셋을 생성하는 디플로이먼트를 정의하거나 기존 디플로이먼트를 제거하고, 모든 리소스를 새 디플로이먼트에 적용할 수 있다.
kubectl api-resources | grep deployments
kubectl explain deploy.sepc
대부분의 컨트롤러는 apps그룹에 속해있다.
디플로이먼트가 소유하는 레플리카셋은 관리하지 말아야 한다. 사용자의 유스케이스가 다음에 포함되지 않는 경우 쿠버네티스 리포지터리에 이슈를 올릴 수 있다.
vi myapp-deploy-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
labels:
app: myapp-deploy
spec:
strategy:
type: RollingUpdate # default 값
rollingUpdate:
maxUnavailable: 1
maxSurge: 1 # 급증, 순간적으로 복제본의 수의 +1(25%)까지 가능
minReadySeconds: 20
replicas: 3
selector:
matchLabels:
app: myapp-deploy
template:
metadata:
labels:
app: myapp-deploy
spec:
containers:
- image: ghcr.io/c1t1d0s7/go-myweb:v1.0
name: myapp
ports:
- containerPort: 8080
kubrctl create -f myapp-deploy-v1.yaml
kubectl get deploy
kubectl get rs
kubectl get pods
구조: deploy -> RS -> pod 여러개
처음에는 v1의 복제본 3개로 구성되어있다 새로운 이미지(v2)로 바꾸니까 복제본이 이 버전으로 순차적으로 바뀐다. 새로운 버전을 배포할 수 있게 해주는 역할을 한다.
이미 떠있는 파드를 수정하는 것이 아닌 파드의 템플릿을 수정한 것이다. 따라서 이는 파드를 새롭게 만들때 적용이 되는 것이다.
yaml 파일 수정 ->
# 바꾼 내용의 파일로 deploy 대체
kubectl replace -f yaml파일
kubectl apply -f yaml파일
# vi로 수정
kubectl edit deploy <NAME>
# json 파일형식을 수정
kubectl patch deploy <NAME> {json}
# 이미지만 수정
kubectl set image <TYPE> <NAME> <CONTAINER_NAME>=<NEW-IMAGE>
# 디플로이먼트의 myapp-deploy의 상태를 확인하는 명령
kubectl rollout status deployment myapp-deploy
# 이미지를 바꾸고 실시간으로 어떻게 변화하는지 확인해보기
kubectl set deployment myapp-deploy myapp=imageghcr.io/c1t1d0s7/go-myweb:v2.0
# 개정판 정보를 확인, 업데이트를 할때마다 저장되고 과거의 버전으로 돌아갈 수도 있음
kubectl rollout history deployment myapp-deploy
# 과거 버전으로 돌아가는 명령
kubectl rollout undo deployment myapp-deploy --to-revision 2
# 확인
kubectl rollout status deploy myapp-deploy
kubectl rollout history deploy myapp-deploy
# From V2 to V3
kubectl apply -f myapp-deploy-v3.yaml
# 여기는 어노테이션이 들어가 있어서 history를 확인할 때 주석이 달린다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
labels:
app: myapp-deploy
annotations:
kubernetes.io/change-cause: My Golang Web App Version 3 # 주석
spec:
...
어플리케이션을 새로운 버전으로 배포
디플로이먼트은 레플리카셋과 큰 차이는 없다. 주로 차이는 staragy(배포 전략) 이 부분이며 레플리카셋의 확장버전이라고 보면 된다.
쿠버네티스에서는 Recreate, Ramped (also known as rolling-update or incremental) 이 2가지 방법을 지원한다. 기본은 rolling-update이다
# 2가지 방법 지원가능한 것 확인
kubectl explain deploy.spec.stragy
재생성 전략은 버전 A를 종료한 다음 버전 A를 끈 후 버전 B를 배포하는 것으로 구성된 더미 배포이다. 이 기술은 응용 프로그램의 종료 및 부팅 시간에 따라 달라지는 서비스의 가동 중지 시간을 의미한다.
다운타임
planed: 웹사이트의 서비스 점검시간 등 , recreate는 이것만 사용가능
unplaned: 천재지변으로 인해 서비스가 강제로 종료되는 등
점진적 배포 전략은 모든 인스턴스가 롤아웃될 때까지 인스턴스를 차례로 교체하여 애플리케이션 버전을 천천히 롤아웃하는 것으로 구성됩니다. 순차적으로 업데이트하는 것을 의미한다.
일반적으로 다음 프로세스를 따릅니다. 로드 밸런서 뒤에 버전 A의 풀이 있으면 버전 B의 인스턴스 하나가 배포됩니다. 서비스가 트래픽을 수락할 준비가 되면 인스턴스가 풀에 추가됩니다. 그런 다음 버전 A의 한 인스턴스가 풀에서 제거되고 종료됩니다.
Ramped 동작과정
데몬셋도 마차가지로 배포전략을 가지고 있다.
- updateStrategy: rollingUpdate이기에 기본적으로 순차적으로 하나씩 바뀌게 된다. 여기서는 Ondelete를 사용할 수 있다.
kubectl explain ds.spec.updateStrategy
스테이트풀셋은 애플리케이션의 스테이트풀을 관리하는데 사용하는 워크로드 API 오브젝트이다. 스테이트풀셋도 kubectl explain stateful.spec.updateStrategy 배포전략이 존재한다.
파드 집합의 디플로이먼트와 스케일링을 관리하며, 파드들의 순서 및 고유성을 보장한다.
디플로이먼트와 유사하게, 스테이트풀셋은 동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다. 디플로이먼트와는 다르게, 스테이트풀셋은 각 파드의 독자성을 유지한다. 이 파드들은 동일한 스팩으로 생성되었지만, 서로 교체는 불가능하다. 다시 말해, 각각은 재스케줄링 간에도 지속적으로 유지되는 식별자를 가진다.
스토리지 볼륨을 사용해서 워크로드에 지속성을 제공하려는 경우, 솔루션의 일부로 스테이트풀셋을 사용할 수 있다. 스테이트풀셋의 개별 파드는 장애에 취약하지만, 퍼시스턴트 파드 식별자는 기존 볼륨을 실패한 볼륨을 대체하는 새 파드에 더 쉽게 일치시킬 수 있다.
스테이트풀셋은 다음 중 하나 또는 이상이 필요한 애플리케이션에 유용
상태가 없는 앱, 웹 같은 경우 디플로이먼트로 배포하고 상태가 있는 DB같은 경우 스테이트풀셋으로 배포한다.
스토리지를 제공하는 목적은 데이터를 저장하기위한 목적이 아닌 앱, 웹 컨텐츠가 볼륨에 있고 이를 가져오기 위함이다. 만들어낸 데이터는 DB에 넣기에 따로 가지고 있지 않는다.
볼륨이 있다고 해서 상태를 저장하는 용도로 쓰는 것이 아닌, 상태가 없는 것들은 이 정보를 가지고 클라이언트에게 제공해준다고 보면 된다. 같은 스토리지를 바라보고 있기 때문에 그것들은 상태가 같다.
vi myapp-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myapp-sts
spec:
selector:
matchLabels:
app: myapp-sts
serviceName: myapp-svc-headless # 헤드리스 서비스 리소스의 이름
replicas: 2
template:
metadata:
labels:
app: myapp-sts
spec:
containers:
- name: myapp
image: ghcr.io/c1t1d0s7/go-myweb:alpine
ports:
- containerPort: 8080
vi myapp-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-headless
labels:
app: myapp-svc-headless
spec:
ports:
- name: http
port: 80
clusterIP: None # 헤드리스 서비스 정의
selector:
app: myapp-sts
kubectl create -f myapp-sts.yaml -f myapp-svc-headless.yaml
# 스테이트 풀셋을 보면 2개 준비완료
kubectl get sts,svc,ep
# 컨트롤러 뒤에 번호가 붙음 -> 고유성을 가지고 있음
kubectl get pod -o wide
kubectl run nettool --image ghcr.io/c1t1d0s7/network-multitool -it --rm
# 호스트 질의 - 서비스의 이름지정
host myapp-svc-headless
# 스테이트풀셋 + 헤드리스, -> 파드의 이름으로 통신해 파드의 IP 반환
host myapp-sts-0.myapp-svc-headless
host myapp-sts-1.myapp-svc-headless
스테이트풀셋은 레플리카 셋과 동일하게 복제본을 제공하는 목적은 같지만 각 파드가 고유한 상태를 가져야 한다. 이를 위해 별도의 상태를 가져야 하기 때문에 스토리지를 따로 가지고 있어야 한다.
모든 파드가 같은 PVC를 보고 있으면 안된다.
vi myapp-sts-vol.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myapp-sts-vol
spec:
selector:
matchLabels:
app: myapp-sts-vol
serviceName: myapp-svc-headless
replicas: 2
template: # 파드의 템플릿
metadata:
labels:
app: myapp-sts-vol
spec:
containers:
- name: myapp
image: ghcr.io/c1t1d0s7/go-myweb:alpine
ports:
- containerPort: 8080
volumeMounts:
- name: myapp-data
mountPath: /data
volumeClaimTemplates: # PVC의 템플릿
- metadata:
name: myapp-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
storageClassName: nfs-client
# 위에서 만들어둔 헤드리스를 다시 만들어준다.
# 현재 pv와 pvc는 없는 상태
# 생성
kubectl create -f myapp-sts-vol.yaml
kubectl get po,svc
# pvc,pv가 생성됨
kubetctl get pv,pvc
# 복제본 늘리기
kubectl scale sts myapp-sts-vol --replicas 4
kubetctl get pv,pvc
kubectl get po,sts
# 복제본 줄이기
kubectl scale sts myapp-sts-vol --replicas 2
kubetctl get pv,pvc
kubectl get po,sts
DB 이중화를 위해서 어떤 파드를 promary로 하고 어떤 것을 secondary를 할지 정하기 위해서 어디서 정보를 저장할지 알아야한다. 따라서 헤드리스 서비스를 통해 어떤 파드에 접근할수 있는지 알아야 하기때문에 스테이트풀셋으로 구성해야한다.
vi mydb-cm-mysql.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mydb-config
labels:
app: mydb
app.kubernetes.io/name: mydb
data:
primary.cnf: |
[mysqld]
log-bin
replica.cnf: | # my.cnf 파일을 대체하기 위함
[mysqld]
super-read-only
vi mydb-sts-mysql.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mydb
spec:
selector:
matchLabels:
app: mydb
app.kubernetes.io/name: mydb
serviceName: mydb
replicas: 2
template:
metadata:
labels:
app: mydb
app.kubernetes.io/name: mydb
spec:
initContainers: # 초기화 컨테이너가 2개 있음
- name: init-mysql # 1번
image: mysql:5.7
command:
- bash
- "-c"
- |
set -ex
[[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/primary.cnf /mnt/conf.d/
else
cp /mnt/config-map/replica.cnf /mnt/conf.d/
fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map
- name: clone-mysql # 2번
image: gcr.io/google-samples/xtrabackup:1.0
command:
- bash
- "-c"
- |
set -ex
[[ -d /var/lib/mysql/mysql ]] && exit 0
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
ncat --recv-only mydb-$(($ordinal-1)).mydb 3307 | xbstream -x -C /var/lib/mysql
xtrabackup --prepare --target-dir=/var/lib/mysql
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ALLOW_EMPTY_PASSWORD
value: "1"
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
livenessProbe:
exec:
command: ["mysqladmin", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
- name: xtrabackup # 실행되고 있는 중에 스토리지의 동기화를 해주는 역할
image: gcr.io/google-samples/xtrabackup:1.0
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
rm -f xtrabackup_slave_info xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm -f xtrabackup_binlog_info xtrabackup_slave_info
echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
fi
if [[ -f change_master_to.sql.in ]]; then
echo "Waiting for mysqld to be ready (accepting connections)"
until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
mysql -h 127.0.0.1 \
-e "$(<change_master_to.sql.in), \
MASTER_HOST='mydb-0.mydb', \
MASTER_USER='root', \
MASTER_PASSWORD='', \
MASTER_CONNECT_RETRY=10; \
START SLAVE;" || exit 1
mv change_master_to.sql.in change_master_to.sql.orig
fi
exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mydb-config
volumeClaimTemplates: # 스토리지 클래스가 없기 때문에 default로 하나가 지정되어있어야함
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
init-mysql: 설정파일을 가져옴,
clone-mysql: 상대방의 DB에서 데이터를 동기화 해서 가져오는 역할을 함
# 일반 서비스
mydb-svc-read.yaml
apiVersion: v1
kind: Service
metadata:
name: mydb-read
labels:
app: mydb
app.kubernetes.io/name: mydb
spec:
ports:
- name: mysql
port: 3306
selector:
app: mydb
app.kubernetes.io/name: mysql
# 헤드리스, 쓰기작업이나 업데이트를 위함
mydb-svc-write.yaml
apiVersion: v1
kind: Service
metadata:
name: mydb
labels:
app: mydb
app.kubernetes.io/name: mydb
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mydb
app.kubernetes.io/name: mydb
# 전체 실행
kubectl create -f .
# 헤드리스와 일반 서비스 2개 생성
kubectl get svc
# 초기화 컨테이너가 존재하기때문에 시간이 좀 걸림
kubectl get pod,pv,pvc
# 테스트
kubectl run db-client --image ghcr.io/c1t1d0s7/network-multitool -it --rm
# 헤드리스
host mydb
# 파드 지정
host mtdb-0.mydb
# 일반 서비스
host mydb-read
mysql -h mydb-0.mydb -u root -p
>show databases;
>CREATE DATABASE mydb
>CREATE TABLE mydb.mytb (message VARCHAR(100));
>INSERT INTO mydb.mytb VALUES ( "Hello world");
>SELECT * FROM mydb.mytb;
exit
mysql -h mydb-1.mydb -u root -p
>show databases;
>SELECT * FROM mydb.mytb
# 명령형으로 보기
mysql -h mydb-1.mydb -u root -e 'SELECT * FROM mydb.mytb'
exit
# 2번 파드가 하나 새롭게 생겼음
kubectl scale sts mydb --replicas 3
# 작업
kubectl run db-client --image ghcr.io/c1t1d0s7/network-multitool -it --rm
mysql -h mydb-2.mydb -u root -e 'SELECT * FROM mydb.mytb'