백준봇 개발기(3) - Kustomize, Argo CD를 통한 K8s 기반 CI/CD 구성

Jiwan Ahn·2023년 11월 9일
0

백준봇 개발기

목록 보기
3/3
post-thumbnail

정말 어렵지만, 정말 도움 많이 준 쿠버네티스...

저번 이야기

백준봇 코드가 수정될 때마다 일일이 변경사항을 EC2에 접속하면서 변경하는 것이 너무 번거로워서, github actions를 통한 아주 간단한 CI/CD를 만들었다. Git에 커밋을 푸시할 때마다, 워크플로우가 동작하면서 컨테이너를 교체하는 방식은 매우 간단 했으나, 문제는 이미지를 교체하면서 발생하는 딜레이였다.

이미지를 교체하기 위해 컨테이너와 이미지를 삭제하고, 새로운 이미지를 pull하는 동안은 돌아가고 있는 서버 컨테이너가 없다. 따라서, 그 동안은 백준봇이 작동을 하지 않는다.

이 점은 내가 push를 하고 백준봇을 테스트하고 있는 과정에서, 명령어가 먹지 않는 것을 발견하고 깨달은 점이다. 따라서, 저번에 쿠버네티스를 공부하면서 배웠던 배포 방식 중, Blue/Green 배포를 적용하기로 했다.


Blue/Green 배포

내가 생각한 아주 적절한 방법은 Blue/Green 배포였다. 즉, 두 개의 컨테이너를 동시에 만들고 구버전의 컨테이너는 꺼놓는 다음, 업데이트가 되면 꺼놓은 컨테이너를 업데이트하고 바로 교체하는 방식이다.

사실, 엄밀히 말하면 완벽한 Blue/Green 배포는 아니다. 원래 Blue/Green 배포란, 구버전과 신버전의 리소스를 미리 생성하고, 업데이트가 되면 그 컨테이너를 가리키고 있는 서비스의 엔드포인트를 곧바로 신버전의 컨테이너로 변경하는 방식이다.

단순히 컨테이너를 교체하는 방식은 Blue/Green 배포 보다는 Rolling Update가 좀 더 맞지만, 당시 Docker Compose를 이용해 두 개의 컨테이너를 동시에 운용하고 있어서 Blue/Green 배포를 사용한다고 한 것이다.

내가 생각한 방식을 그림으로 나타내면 다음과 같다.

즉, ISGREEN 이라는 boolean 환경변수를 지정하고, 푸시를 할 때마다 true/false 를 스위칭 하면서, 조건문 분기점에 따라 켜놓는 컨테이너를 다르게 한 것이다. 이런 방식은 내가 궁극적으로 원했던 무중단 배포를 실현할 수 있게끔 했다.

그렇게 하기 위해서는 쉘 스크립트를 실행해야 했으며, 미리 Ec2에 내가 만든 Shell 스크립트를 만든 후, Github actions에서 원격으로 이 스크립트를 진행하는 방식을 선택했다.

IS_GREEN=$(docker ps | grep boj-green-container) # 현재 실행중인 App이 blue인지 확인

if [ -z "$IS_GREEN"  ];then # blue라면

  echo "### BLUE => GREEN ###"

  echo "1. Get Latest Baekjoon-bot-image"
  sudo docker-compose pull boj-green-service

  echo "2. green container up"
  sudo docker-compose --env-file=env.list up -d boj-green-service  # green 컨테이너 실행

  echo "3. blue container down"
  sudo docker-compose stop boj-blue-service

else
  echo "### GREEN => BLUE ###"

  echo "1. get blue image"
  sudo docker-compose pull boj-blue-service

  echo "2. blue container up"
  sudo docker-compose --env-file=env.list up -d boj-blue-service

  echo "3. green container down"
  sudo docker-compose stop boj-green-service
fi

생각지도 못한 로그

그렇게 무중단 배포를 실현하고, 잘 작동하고 있는지 EC2에 들어가서 로그를 확인하려고 했는데 웬걸? 텅텅 비었었다.

뭐지..? 하고 했던 찰나, 나는 순간 로그 파일들은 컨테이너 안에서만 저장되고 있었다는 것을 깨달았다. 즉, volume 설정을 안한 것이다.

컨테이너가 삭제되면, 그 안의 데이터까지도 모두 삭제가 된다. 따라서, 데이터를 보존하기 위해서는 따로 volume을 설정한 후, Host 머신에 파일들을 저장해야 한다. 다행히 Docker Compose는 volume을 지정하는 기능이 있어 바로 저장을 했다.

  boj-blue-service:
    container_name: boj-blue-container
    image: synoti21/baekjoon-bot:latest
    env_file:
      - env.list
    volumes:
      - ubuntu_log_volume:/app/logs
volumes:
  ubuntu_log_volume:
    external: true

완돌이의 탄생

잘 배포되고 있던 백준봇의 배포과정에 거대한 변화가 생긴 건 바로 완돌이의 탄생이었다. 저번 글에서 내가 쿠버네티스 기반 서버를 구성하는 글을 적은 적이 있었을 것이다. 설치 과정에서 날 화병 걸리게 만든 썰에 대해서는 다음 글에 적을 것이지만, 어찌저찌해서 성공적으로 쿠버네티스 기반 서버를 만든 나는, EC2에서 홈서버로 백준봇을 옮기고 싶었다.

여기서 내 저번 글을 읽은 사람은 알 것이다. 내가 Ec2에 경량화된 쿠버네티스 (k3s) 설치를 시도했다고. 물론, 리소스 때문에 실패했지만.

하지만, 지금은 난 쿠버네티스를 운용하고 있다. 그말은? 이제 진짜 내가 처음에 말했던 그 쿠버네티스 기반 파이프라인을 구성할 수 있다는 것이다!!


나름 이유 있던 K8s 선택

그럼 내가 "어? 쿠버네티스 홈서버가 있으니 이참에 옮길까? 라는 단순한 생각으로 그 어려운 길을 선택헀을까? 그건 아니다. 난 항상 이유가 부족하면 행동에 옮기지 않는다. 내가 굳이 쿠버네티스 기반 홈서버에 백준봇을 호스팅한 건 나름 납득할 만한 이유가 있었다.

모니터링 가능

내가 쿠버네티스로 홈서버를 구성했을 때는 쿠버네티스 기반 파이프라인을 구축하기 위해 Jenkins, Argo CD를 설치한 상태였다. 물론 Jenkins는 파드로 띄워져 있는 상태라 도커 이미지 빌드 불가능 사태가 벌어져 직접 빌드는 하지 못했지만, Argo CD는 완전하게 돌아가는 상태였다.

Argo CD는 쿠버네티스로 배포하고 있는 서비스의 구조를 시각적으로 잘 표현해준다. 검정 터미널 화면으로 일일이 명령어를 쳐야만 하는 불편한 상황에서, Argo CD는 현재 배포중인 서비스가 제대로 돌아가고 있는지 한눈에 확인할 수 있게 했다.

더군다나, 현재 백준봇은 완벽한 상태가 아니라 한창 개발중인 상태라, 가끔 알 수 없는 이유로 지 혼자 죽는 일이 허다했다. 따라서, 현재 백준봇이 제대로 가동되고 있는지 한 눈에 확인해야 할 필요가 있었다.

위의 UI는 백준봇의 배포 구조 뿐만 아니라, 현재 파드가 "Healthy" 상태, 즉 제대로 돌아가고 있는지도 보여줬다. 내가 굳이 EC2에 직접 들어가서 로그를 뜯어보고 왜 죽었는지를 하나하나 볼 필요가 없었다는 뜻이다.

Self Healing

쿠버네티스의 가장 매력적인 포인트는 바로 자동화된 컨테이너 관리다. 내가 "자동화"를 그렇게 좋아하는 이유가, 일일이 손수 작업해야 하는 일을 알아서 해주는 것처럼, 쿠버네티스도 컨테이너 형태로 작동하고 있는 서비스를 알아서 관리해준다.

완돌이: 얘 상태가 이상해요 주인님!

만약 서비스에 뭔가 문제가 생겼다면, 일단 'Degraded'로 문제가 생겼다고 표시해주고, 위의 사진처럼 알아서 컨테이너를 재시작하려고 한다. 즉, 일시적으로 우리가 예외처리를 안 한 곳에서 터지거나, 알 수 없는 이유로 서버가 다운될 때, 알아서 재시작을 해준다.

자동으로 업데이트

뭐니뭐니해도 가장 큰 장점은, 알아서 내 깃허브 레포지토리와 연동시켜준다. 기본적으로 Argo CD는 GitOps를 위해 만들어진 오픈소스다. 즉, 배포와 관련된 모든 것을 코드로, Git에서 관리하는 툴이다.

깃허브에 있는 manifest 파일, 쉽게 말하면 쿠버네티스 yaml 파일에 변경사항이 생기면 이를 자동으로 인식하고 오브젝트를 최신화 한다.

"근데 이걸 어떻게 적용함?"

하지만 Argo CD는 코드의 변경사항을 모른다.

이 점이 좀 걸렸다. 기본적으로 Argo CD는 Manifest 파일의 다른 점을 인식하고, 이를 동기화하지만, 코드의 변경사항은 감지를 하지 않는다. 결국, 이 점은 내가 직접 구현을 해야했다.


Kustomize 도입

그렇다는 말은 깃허브 레포지토리에 있는 manifest 파일을 자동으로 수정을 해줘야 한다는 의미인데, 이걸 대체 어떻게 해야할 지 몰랐다.

Github actions가 그나마 떠올랐는데, 깃허브 액션이 레포지토리를 직접 수정할 수 있었나..? 라는 생각이 떠올랐다. 즉, manifest파일을 직접 수정할 수 있느냐 이말이다.

설령 수정이 가능하다 하더라도, 어느 부분을 어떻게 바꿀 것인가가 또 고민거리였다. Argo CD는 manifest 파일의 다른 점을 인식하고 이를 동기화하는 방식으로 사용하는데, 뭘 바꿔야 Argo CD가 다른점을 인식하지 라는 생각을 했다.

리소스 관리를 위한 Kustomize

그러다가 이 녀석을 발견했다. 기존의 manifest 파일을 수정하지 않고, 리소스를 관리해주는 툴임을 발견했다. 공식문서는 Kustomize를 다음과 같이 소개하고 있다.

  • Kustomize는 템플릿이 없는 방식으로 구성 파일을 커스터마이징 하는 데 도움이 됩니다.
  • Kustomize는 커스터마이징을 더 쉽게 하기 위해 생성기(Generator)와 같은 여러 편리한 방법을 제공합니다.
  • Kustomize는 패치(Patch)를 사용하여 기존 표준 구성 파일을 방해하지 않고 환경별 변경 사항을 도입합니다.

즉, manifest 파일을 건드리지 않고 직접 리소스를 관리해주는 역할을 한다는 뜻이다. 그렇다면 이를 어떻게 활용할까?

이미지 태그

활용을 하기 전, Argo CD의 작동 방식을 알아야 한다.

  • Argo CD는 코드의 변경사항을 감지하지 못한다.
  • 대신 선언형 템플릿 (yaml) 의 변경사항을 감지하고 이를 동기화한다.
  • 커밋을 할 때마다 새로운 이미지를 빌드해야 한다.
  • 그렇다면 유일한 변경사항은 이미지다!

이에 대한 결론은, 결국 Kustomize로 이미지를 건드려야 한다는 것이다. 허나, manifest 파일의 이미지의 태그는 :latest로 고정되어있다.

containers:
  - name: baekjoon-bot
    image: synoti21/baekjoon-bot:latest
    imagePullPolicy: Never

다시, Kustomize는 뭐라고? manifest의 파일을 건드리지 않고 오브젝트들의 리소스를 관리한다. 즉, 이미지까지도 관리한다는 말이다. 그렇다는건, Kustomize로 이미지 태그를 지정해주면 된다. 이렇게!

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- manifest.yaml
images:
- name: synoti21/baekjoon-bot
  newName: synoti21/baekjoon-bot
  newTag: f31d3dc5

저 해시값처럼 보이는 태그는 어디서 나온걸까? 바로, 커밋의 해시값이다! Git은 커밋을 할 때마다 고유 해시값을 부여한다. 난 이것을 이용하여 이미지의 태그를 계속해서 변경했다. 결국 이미지를 빌드 할 때마다 커밋의 해시값을 태그로 적용시키면 결국 kustomization.yaml에 변경사항이 생긴다!

Github actions를 통해 이미지를 빌드하고, kustomize build를 통해 image 태그를 업데이트 한 후, 추가로 커밋하면 이미지 태그는 계속해서 변한다.자동으로 커밋이 추가되고, 다음과 같이 태그가 변경이 된다. Argo CD는 "어? yaml에 변화가 생겼네? 하고 이미지를 새로 pull한다.' 그럼 자연스럽게 최신 이미지로 파드를 교체하게 된다!!


완벽한 CI/CD

Kustomization의 변경사항을 잘 감지하는 모습.

쿠버네티스를 이용한 CI/CD는 그야말로 완벽한 CI/CD 파이프라인이었다. 장점이 정말로 많은 데 체감상으로 큰 것만 나열하면:

  • Autoscaling 과 같은 부가적인 기능 추가
  • 알아서 망가진 컨테이너 재시작
  • manifest 파일의 수정사항이 없으므로, manifest의 수정 용이
  • Grafana, Prometheus로 실시간 리소스 모니터링 가능

일단, 기존에 사용했던 Docker Compose에 비해 지원하는 기능이 매우 많아서 후에 어떤 기능이 필요하면 손쉽게 확장이 가능하다. 그 기능들을 조금 자세하게 풀어보겠다.

AutoScaling


백준봇은 아니지만 동일하게 CI/CD를 적용했던 다른 프로젝트를 예로 들면, 평소에는 replica 수를 2개로 두었다가, 트래픽이 증가하면 리소스 임계값을 초과할 경우 replica 수를 증가시키는 Autoscaling이 가능했다.

쿠버네티스에는 기본적으로 Pod Autoscaling을 HPA (Horizontal Pod Autoscaling)를 통해 구현이 가능하다. Deployment에 CPU와 메모리 상한치를 지정하고, 만약 (예를 들어) 50%를 넘으면 레플리카 수를 증가시킴으로써 유연한 리소스 컨트롤이 가능하다.

우리 완돌이는 슈퍼컴퓨터가 아니기 때문에 CPU나 메모리가 한정적이다. 더군다나 서버 하나만 배포하고 있는 것도 아니고, 다른 K8s 애드온 까지 합하면 거의 10개를 동시에 띄우고 있다. 그만큼 리소스 분배가 정말 중요한데, 트래픽이 과도하게 몰릴 때만 replica 수를 늘리면 매우 효율적인 리소스 분배가 가능하다.

Monitoring

몇 개 띄우지도 않았는데...ㅠ

무엇보다도 우리 완돌이가 과부화되지 않도록 실시간으로 모니터링을 해야 했는데, 쿠버네티스 CI/CD를 이용해서 내부 클러스터에 배포를 하면, 해당 서버까지 모니터링 대상이 돼서 얼마나 리소스를 잡아먹고 있는지 확인이 가능하다.

가끔 할당 가능한 CPU가 충분하지 않아 파드가 Pending 상태가 되는 때가 있다. 그럼 Grafana 같은 툴로 위의 사진과 같이 할당 가능한 리소스를 볼 수가 있다.

Self Healing

나도 모르는 사이에 재시작을 두번이나 했다.

가장 좋은 건, 알 수 없는 오류로 컨테이너가 망가질 때마다 알아서 재시작을 해주는 것이 너무 편했다. 덕분에 난 저 파드가 재시작한지도 모르고 정상적으로 서비스를 사용할 수 있었다. 왜냐? 재시작은 쿠버네티스가 알아서 하기 때문에!


이제 남은건?

여기까지 들어보면 이제 과자나 먹으면서 가만히 있겠지만, 아직 몇 가지가 남았다.

  • 그래도 느린 Github actions의 속도
  • 완전한 무중단 배포

첫번째, Github actions는 여전히 정~말 느렸다. 도커 이미지 빌드만 세월아 네월아 걸리기 때문에 기본 워크플로우 동작만 거의 2분이 걸렸다.

그래서 지금 내 계획은 Jenkins를 쿠버네티스 위에 띄우고 Jenkins로 빌드를 시작하는 것이다. 일단 완돌이 CPU가 그래도 굉장히 성능이 좋은 CPU라 Github actions보다 빠를 것이라 예상되고, 무엇보다도 그냥 내가 Jenkins를 써보고 싶었다. (느린 건 좀 핑계처럼 보이네...)

그리고 두번째로, 한 가지 문제점을 좀 늦게 발견했는데, 완전한 무중단 배포는 아직 아니었다. 이건 백준봇에게는 일어나지 않았고, 다른 Springboot 프로젝트에서 발견됐는데, 이건 나중에 조만간 "Springboot는 은탄환이 아니다" 라는 글에서 다룰 예정이다.

조금 힌트를 주자면, Springboot는 정말 매우매우매우매우 무겁다. 내가 Springboot 서버를 완돌이 위에 띄우기 꺼려했던 이유이기도 하다.

결론

아무튼 우여곡절 끝에 이렇게 쿠버네티스 기반 CI/CD 파이프라인을 구성하니, 개발속도가 확연하게 빨라졌다. 서버 중단 횟수가 일단 굉장히 줄어들어서 나중에 다른 프로젝트에서 API 연결과 같이 서버와 계속 통신을 해야할 때는 정말로 유용했던 것 같다.

무엇보다도 모니터링도 가능하면서 자가 치유 기능도 있다는 게 서버 유지보수 할 때 굉장히 효과적이었다.

이게 참 이상한게, 분명 백준봇 코드를 일일이 옮기기 귀찮아서 CI/CD를 구성한 건데, 이게 지금 쿠버네티스까지 영역이 확장된 게 좀 웃기긴 하다. 하지만, 결국 실보단 득이 굉장히 많았고, 다른 프로젝트에서도 유용하게 써먹고 있어서 개인적으로는 성취감이 정말 정말 컸다.

완돌이 최고!

profile
Engineer, to be a Pioneer.

0개의 댓글