
디플로이먼트가 레플리카셋을 만들고 레플리카셋이 정의된 개수만큼 파드를 만드는 구조이다.
이미지의 새로운 버전이나 새로운 이미지를 배포/교체 하는 것을 쉽게 해준다
= 배포 전략 
쿠버네티스에서 지원하는 배포전략은 recreate와
rolling update( =roll out, Ramped) 두가지가 있다. 
쿠버네티스의 디플로이먼트는 default로 rolling update를 가진다.
< recreate >
💡 네이버,구글 같은 포털사이트도 옛날에는 recreate를 사용했기 때문에 planned 다운타임이 있었지만 현재는 무중단 시스템을 구현했다.
< rolling update >
-> 템플릿 수정을 통해 새로운 버전이 적용된 레플리카셋을 만들어놓고 새로운 버전의 파드를 하나 생성
-> 이후 다시 새로운 버전의 파드를 생성 후 이전 버전의 레플리카셋의 파드를 제거해 나간다.
-> 이런식으로 새로운 버전의 레플리카셋을 생성한다. 
1 . 디플로이먼트 예시
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy
  labels:
    app: myapp-deploy
spec:
  strategy: # 배포전략
    type: RollingUpdate # or recreate
    rollingUpdate: # rolling update 선택시 옵션 선택 
      maxUnavailable: 1 
      maxSurge: 1
  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
kubectl replace -f myapp-deploy-v1.yaml # 새로운 버전으로 수정한 템플릿으로 교체 
kubectl set image < TYPE > < NAME > < CONTAINER_NAME >=< NEW_IMAGE >-> 새로운 버전의 레플리카셋이 생성되고, 기존의 레플리카셋의 파드가 하나씩 없어지면서 새로운 버전의 레플리카셋에 새로운 버전의 이미지가 적용된 파드가 하나씩 생성되는 것을 확인할 수 있다.
리소스로 레플리카셋을 사용하면 템플릿에 버전을 변경후 replace를 하게되면 이미 생성되어있는 기존의 파드에는 영향이 없고, 새로운 버전의 레플리카셋이 생성이 되면서 앞으로 생성되는 파드에만 영향을 준다
-> 파드를 지우고 다시 생성하면 새로운 버전의 파드가 생성되기는 한다.
-> 하지만 파드의 개수가 많다면 새로운 버전을 배포하기 위해서 일일이 파드를 지우는 것은 비현실적이다,
-> 디플로이먼트를 사용하게 되면 기존의 파드(서비스,어플리케이션)에 새로운 버전을 배포/교체 하기 쉬워진다.
kubectl rollout status deployment < delpoyment name >
# 배포가 잘되었는지 안되었는지 상태를 확인할 수 있다.
kubectl rollout history deployment < delpoyment name >
# 배포를 한 기록 확인, 개정판과(버전)과 변경사유를 볼 수 있다.
# history를 보고 rollback을 할 수 있다.    
-> 어노테이션을 추가해 변경사유를 기록해놓을 수 있다. 
kubectl rollout undo deployment < delpoyment name > --to-revision 2
# = rollback 
2 . 디플로이먼트 예시
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:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  minReadySeconds: 20
  replicas: 3
  selector:
    matchLabels:
      app: myapp-deploy
  template:
    metadata:
      labels:
        app: myapp-deploy
    spec:
      containers:
      - image: ghcr.io/c1t1d0s7/go-myweb:v3.0
        name: myapp
        ports:
        - containerPort: 8080
        readinessProbe:
          periodSeconds: 1
          httpGet:
            path: /
            port: 8080
            
1 . 스테이트풀셋 생성
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
1-1 . 서비스 생성
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
-> 헤드리스 서비스는 clusterip가 존재하지 않아 host로 서비스의 이름을 호출하면 서비스의 ip가 return되는 것이 아니라, 서비스와 연결된 파드의 ip가 return된다 = 파드에 직접 접근 가능
-> 반드시 연결하는 서비스는 헤드리스 서비스여야 한다.
-> 다른 리소스로 생성한 파드이름과 다르다는 것을 볼 수 있다.
-> 스테이트풀셋으로 만든 파드는 고유성이 존재하기 때문에 이름이 랜덤하게 생성되지 않고 0부터 순차적으로 생성된다.
-> 만약 파드를 삭제하고 다시 생성해도 삭제했던 파드가 생성되고, 똑같은 노드에 배치된다 = 순서와 고유성 보장
kubectl run nettool --image ghcr.io/c1t1d0s7/network-multitool -it --rm # 가상의 클라이언트 담당 파드 생성 
-> 스테이트풀셋과 헤드리스 서비스를 결합하면 파드의 이름을 통해 접근할 수 있다.
1 . Volume을 부착한 스테이트풀셋 생성
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 # 사전에 설치한 스토리지 클래스 설정 
-> 파드의 템플릿과 pvc의 템플릿을 작성해줘야 한다.
-> sts가 파드의 템플릿에 따라 파드를 만들고 replica개수에 따라 동일한 파드 두개를 생성한다. 그리고 pvc템플릿에 따라 두개의 pvc를 만들고 각 파드에 연결한다. 지정한 스토리지 클래스에 의해 동적 프로비저닝으로 각 pvc에 pv연결을 하고 pv연결을 통해 스토리지에 연결된다 
kubectl scale sts myapp-sts-vol --replicas 3 # 파드의 개수 3개로 scale 
kubectl get pv,pvc # pv,pvc 확인 
-> 3개로 늘리면 자동으로 추가된 하나의 파드에 pvc-pv연결이 생성된다.
kubectl scale sts myapp-sts-vol --replicas 2 # 파드의 개수 2개로 scale 
    kubectl get pv,pvc # pv,pvc 확인 
-> 파드를 다시 두개로 줄여도 기존의 pv-pvc연결은 세개를 유지한다.
->  스테이트풀셋으로 생성한 파드는 고유한 속성을 가지고 이러한 고유한 속성을 볼륨에 저장한다. 따라서 파드를 지웠는데 pv-pvc연결도 없어지면서 데이터도 같이 날아간다면 이것은 고유한 속성을 유지하지 못하는 것이다.
= 파드를 지워도 볼륨은 그대로 존재한다
= 볼륨까지 지우기 위해서는 직접 삭제해야 한다.
= 만약 모든 파드를 지우고 새로운 스테이트풀셋으로 파드를 생성하면 기존의 남아있던 볼륨이 그대로 새로운 파드에 연결되어 데이터를 저장한다.
kubectl delete pvc --all # 남아있는 볼륨(pvc) 모두 삭제 
1 . 컨피그맵 구성
apiVersion: v1
kind: ConfigMap
metadata:
  name: mydb-config
  labels:
    app: mydb
    app.kubernetes.io/name: mydb
data:
  primary.cnf: | 
    [mysqld]
    log-bin    
  replica.cnf: |
    [mysqld]
    super-read-only    
2 . 읽기위한 서비스 구성
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
3 . 쓰기위한 서비스 구성
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
4 . 스테이트풀셋으로 파드(DB서버) 생성
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:
      - name: init-mysql
        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
        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 # primary와 replica를 동기화 
        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:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi
5 . 결과 확인
kubectl run dbclient --image ghcr.io/c1t1d0s7/network-multitool -it --rm
# 가상의 클라이언트 파드 생성 및 접속
mysql -h mydb-0.mydb -u root -p
# 클라이언트가 원격으로 프라이머리 DB서버(파드)에 접속 
# 원격 접속은 -h옵션 사용
CREATE DATABASE mydb
CREATE TABLE mydb.mytb (message VARCHAR(100))
INSERT INTO mydb.mytb VALUES ("hello workd")
# DB생성 및 데이터 입력 
mysql -h mydb-1.mydb -u root -e 'SELECT * FROM mydb.mytb'
# 클라이언트가 원격으로 세컨더리 DB서버(파드)에 EXEC명령으로 테이블에 저장된 데이터 출력
-> 프라이머리와 세컨더리 DB가 동기화 되어있기 때문에 프라이머리에서 입력했던 데이터가 세컨더리에 저장되어있는 것을 확인할 수 있다.