* 본 게시물은 쿠버네티스 어나더 클래스 (지상편) - Sprint 1, 2 강의와 강의 자료를 바탕으로 작성되었습니다.
CI/CD Tools는 어떤 것을 왜 사용해야 하는 지 아는 것이 중요하며, 프로젝트에 따라 적절한 기술을 사용할 수 있어야 한다.
CI/CD Pipeline을 구성할 때 고려해야 할 요소

- 프로젝트를 쿠버네티스로 배포할 때 반드시 Kubectl이 아니더라도 Helm과 Kustomize를 이용하여 배포할 수 있음
- Jenkins Pipeline은 하나의 프로젝트 안에 여러 Jenkins 프로젝트를 연결할 수 있기 때문에, 한 곳에서 여러 프로젝트 작업을 모아 볼 수 있음
하지만, 각각의 담당자가 담당하는 구역이 다르면 소스/컨테이너/배포 단계를 나누기도 한다.
(관리 담당자 입장에서는 Pipeline이 하나 있는 것이나 마찬가지) - 때문에, 관리와 기능 중에 효율적인 것을 선택하는 것이 중요

기존 Jenkins에서 담당하던 빌드&배포를 작업을 ArgoCD를 이용하여 배포 단계를 분리할 수 있음
하나의 ArgoCD에서 개발/운영 서버에 배포를 담당할 수도 있고, Node마다 배포를 담당하는 ArgoCD를 올려 각각 배포할 수도 있음
- 하나의 ArgoCD에서 모든 배포를 담당하는 단일 구성의 경우, 하나의 ArgoCD만 관리하면 되기 때문에 관리가 편하지만, 장애가 발생하면 모든 Node의 배포가 중단된다.
- 각 Node마다 ArgoCD를 두는 분리 구성의 경우, 관리해야 할 ArgoCD가 Node 개수에 비례하여 늘어나게 돼 관리가 힘들지만, 하나의 ArgoCD에서 장애가 발생해도 다른 Node의 배포에는 영향이 없다.

CI/CD Tools에는 온라인에서 제공하는 Tools와 오프라인에서 관리하는 Tools가 있다.
- 온라인의 Tools의 경우 관리하는 서버가 줄어 편하지만, 민감한 데이터도 온라인에 업로드하게 되어 위험할 수 있다.
- 다양한 Tools 중 레퍼런스가 많은 Tools를 선택하는 편이 장애에 대응하기 쉽기 때문에 일반적이다.
- 또한, 유지보수 업체 유무에 따라 Tools를 선택하는 기준이 될 수도 있다.
(유지보수 업체가 없을 경우, 유지보수가 힘들 수 있음)

Container Image로 빌드하기 위해 일반적으로 Docker를 사용하는데, Daemon을 띄워야 사용이 가능하기 때문에 무겁다.
- 대체재로 RHEL의 쿠버네티스 제품에 기본으로 포함된 buildah 라는 것이 있는데 Daemonless라 자원 소모가 적다.
- podman을 이용하여 docker login을 하고 Base Image를 가져올 수 있음
- skopeo를 이용하여 docker image를 업로드
- 마지막으로, ArgoCD를 통해 배포가 되는 방식
- 또한, Kaniko라고 k8s 위의 Container 환경에서 구동되는 것도 있음

Docker가 Daemon을 올려 사용해야 해서 무겁긴 하지만, 레퍼런스가 많아 여전히 많이 사용된다.
배포 전략을 세울 때 고려해야 할 요소
1. Recreate
Deployment에서 기능을 제공

실행 중이던 Pod가 내려가고 새로운 Pod가 만들어지므로, Pod가 완전히 생성되기까지 Downtime이 발생
2. Rolling Update

기존의 Pod를 일부 내리고 새로운 Pod를 조금씩 올리며 업데이트하는 방식이다.
서비스 중단이 발생하지는 않지만, 2가지 버전이 동시에 존재할 수 있음
3. Blue/Green

새로운 Pod 생성 명령을 내리고 완료되기 전까지 기존의 Pod가 계속 살아있다.
새로운 Pod 생성이 완료되면, Service Selctor가 변경되고 기존의 Deployment를 제거한다.
Script를 통해 자동 배포가 가능하며, 수동 배포 시 롤백이 빠르다는 장점이 있다.
하지만, 생성이 완료되면 새로운 Pod에 바로 트래픽이 몰려오게 되므로 과도한 트래픽 유입시 문제가 발생할 수 있다.
때문에, 주로 운영환경에서만 테스트가 가능한 경우 사용되는 방식이며, Service를 새로 만들어 새로운 Pod에 연결하여 테스트를 진행한다.
4. Canary

Ingress라는 Resource가 추가되며, 각 Service의 트래픽 양을 조절해주는 역할을 한다.
새로운 Pod가 생성되면 Ingress의 가중치를 조절하여 유입되는 트래픽의 양을 조절한다.
(특정 헤더 값에 한해서만 새로운 Pod로 트래픽을 유입시킬 수 있다.)
덕분에, Blue/Green 방식처럼 콜드 스타트(한 번에 많은 양의 트래픽이 유입)가 생기는 것을 방지할 수 있다.
덕분에, A/B 테스트가 가능
(A/B 테스트는 배포 전략이 아닌, Canary 배포 상황에서 v1과 v2를 비교하기 위한 테스트 방법론이다.)
무조건 무중단이면 좋은 배포가 아니고, 각 상황에 맞는 배포 방식이 중요하다.
예를 들어 DB Scheme을 변경해야 할 경우, 미리 사용자에게 공지를 하여 서버를 내리고 작업한 후 다시 올리는 것이 좋다.
단계 별로 구축하는 배포 Pipeline
여러 배포 방식이 있지만, 처음부터 고도화된 배포 방식을 설계하기 보다는 이전 단계의 방식이 안정화되면 조금씩 추가하며 구성하는 것이 좋다.
1. Jenkins 기본 구성

각 Step의 Jenkins Job을 따로 만들어 실행하는 방식
2. Jenkins Pipeline 사용

각 Projects(Jenkins Jobs)의 단계별 진행 상황을 가시적으로 표현할 수 있다.
Jenkins Pipeline을 JenkinsFile을 통해 관리할 수 있다.
Script로 구성해야 하기 때문에, 처음부터 Script를 작성하면 오류가 잦을 수 있다. Jenkins에서 제공하는 UI로 먼저 구성하고, 그 구성을 바탕으로 Script를 작성하면 오류를 줄일 수 있다.
3. Kustomize, Helm 배포

기존에는 각 환경별로 배포를 하기 위해 YAML 파일을 복사하여 수정하는 방식을 사용했기 때문에, 반복된 작업이 번거로울 뿐더러 관리해야 할 파일도 환경에 비례해 증가했다.
때문에, Kustomize와 Helm의 등장으로 Package라는 파일을 이용해, 각 항목에 변수를 지정하는 것으로 YAML을 동적으로 수정할 수 있게 되었다.
덕분에 관리해야 할 YAML 파일도 줄어드는 장점도 생김
4. ArgoCD 배포 분리

k8s 위에 Helm과 ArgoCD를 올려 배포하는 것으로, ArgoCD는 기존의 YAML 파일과 릴리즈(원격 저장소)의 YAML 파일을 비교하여 한 곳이 업데이트가 되면 반대쪽에도 반영하는 동기화 기능을 제공한다.
또한, 릴리즈의 YAML 파일이 수정될 경우, 인프라 환경에도 반영하여 배포한다.

또한, ArgoCD는 k8s에서 Resource 간의 관계도를 표현하고 대시보드를 제공한다.
[미션 5]
1. Docker와 Containerd에서의 이미지 다운로드&업로드 차이
1.1. Docker
- 사전 준비
# DockerFile 다운로드
curl -O https://raw.githubusercontent.com/k8s-1pro/install/main/ground/etc/docker/Dockerfile
# App 소스 다운로드
curl -O https://raw.githubusercontent.com/k8s-1pro/install/main/ground/etc/docker/hello.js- 빌드
docker build -t one393/hello:1.0.0 .- 이미지 리스트 조회
docker image list
- 이미지 태그 변경
# one393/hello:1.0.0 이미지롤 one393/hello:2.0.0 이름으로 복사
docker tag one393/hello:1.0.0 one393/hello:2.0.0
- 로그인
docker login -u {UESRNAME}
- 이미지 업로드
docker push one393/hello:1.0.0
- 이미지 삭제
docker rmi one393/hello:1.0.0
- 이미지 다운로드
docker pull one393/hello:1.0.0
- 이미지 -> 파일 변환
docker save -o file.tar one393/hello:1.0.0

- 파일 -> 이미지 변환
docker load -i file.tar
1.2. Containerd
- 네임스페이스 조회
ctr ns list
containerd를 사용하는 k8s 입장에서는 'k8s.io' 네임스페이스의 이미를 사용함
만약, 사용자가 네임스페이스 지정없이 이미지를 다운로드 받으면, Default 네임스페이스에 이미지가 생성되어 k8s에서 사용할 수 없게 된다.
- 특정 네임스페이스 내 이미지 조회
ctr -n k8s.io image list
- 다운로드 및 이미지 확인
# docker hub의 이미지 다운로드
ctr image pull docker.io/one393/hello:1.0.0

- 태그 변경
# 태그를 2.0.0으로 변경
ctr image tag docker.io/one393/hello:1.0.0 docker.io/one393/hello:2.0.0
- 업로드
ctr image push docker.io/one393/hello:2.0.0 --user {USERNAME}
- 이미지 -> 파일 변환
ctr -n default image export file.tar docker.io/one393/hello:1.0.0
- 파일 -> 이미지 변환
# k8s.io 네임스페이스에 file.tar에 저장된 이미지를 불러옴
ctr -n k8s.io image import file.tar
- 이미지 제거
ctr -n k8s.io image remove docker.io/one393/hello:1.0.0
같은 이미지를 Docker와 Containerd로 받았을 때 사이즈가 다른 이유
- Docker (490MB) / MB는 10의 지수 승을 기준으로 함

- Containerd (248.3MiB) / MiB는 2의 지수 승을 기준으로 함

가설 1. Container Image를 만들 때, 플랫폼(amd64, arm64)을 고려해야 하는데, Docker에서는 amd64를 Containerd에서는 arm64를 다운로드 해서 크기가 다를 것이다.
- Docker

- Containerd

Docker에서는 amd64 버전을 다운로드한 것을 확인할 수 있고, Containerd에는 amd와 arm이 둘 다 표시된다.
(Containerd는 해당 이미지가 지원하는 플랫폼을 보여주는 것이기 때문)
실제로는 둘 다 같은 환경으로 amd64를 다운로드할 것이기 때문에, Size가 똑같아야 한다.
또한, Containerd가 진짜로 두 플랫폼을 모두 가져온 것이라면 Docker의 이미지보다 크기가 커야하지만, 더 작다.
때문에, 이 가설은 틀렸다고 볼 수 있다.
가설 2. Container 이미지는 각각 Layer로 구성되어 있는데, Docker는 전체 Layer를 다운로드 받았고, k8s는 기존 이미지에 이미 존재하는 Layer가 있기 때문에 새로 받은 이미지의 크기가 작게 조회될 것이다.
Docker -> Containerd
- 이미지를 파일로 추출하여 Master Node로 전송 (472MB)

- k8s에서 파일을 이미지로 변환 (471.5 MiB)

Containerd -> Docker
- 새로운 이미지를 받아 파일로 추출하려 CI/CD 서버로 전송 (249MB)

- CI/CD 서버에서 파일을 이미지로 변환 (490MB)

Docker에서 Containerd로 파일을 보낼 때는 이미지의 용량 변화가 없었지만, Containerd에서 Docker로 파일을 보내 이미지를 받았을 때는 용량이 증가한 것을 확인할 수 있었다.
Container의 Image는 Layer 방식으로 만들어진다. 새로운 Image를 다운로드 받을 때 기존의 Image가 내가 필요한 Layer를 이미 가지고 있을 경우, 해당 Layer를 공유하고 없는 Layer는 새로 다운로드 받아 자신만의 Image를 만듦
공통되는 Layer를 공유하는 덕분에, Disk 공간을 절약할 수 있음
그럼, Containerd에서 Docker로 이미지를 가져갈 때 이미지의 용량이 증가한 것은 Docker에서 뭔가 필요한 Layer를 추가로 다운로드 했을 것이라고 유추할 수 있음
가설 3. k8s에는 다른 Runtime을 사용할 수 있고 같은 Image더라도 사용하는 Container Runtime에 따라 Image의 크기가 달라질 것이다.
- Docker는 Containerd를 이용하여 Container를 만드는데, Docker가 그에 대해 제공하는 기능들이 많음
- Docker는 추가적인 기능 제공을 위해, 실제 이미지에 자신의 메타데이터 규격에 맞추어 데이터를 추가하여 이미지를 재구성하기 때문에 용량이 증가하게 됨
- 반대로, Docker에서 Containerd로 이미지를 가져가면 Docker가 추가한 메타데이터도 같이 전달되기 때문에 Containerd는 불필요한 데이터를 포함한 이미지를 갖게 됨
'Tech > Kubernetes(K8s)' 카테고리의 다른 글
잘못된 정보가 있다면 말씀해주세요!