Bullshitting Blog

개소리하는 블로그

[ architecture ]

Simple is the best

simple-is-the-best.md

이전까지는 회사에서 제품 설치 난이도를 줄이기 위해 쿠버네티스 설치를 간편하게 하는데 집중하고 있었다. 그러다가 문득, 쿠버네티스를 사용하는 이유에 대해 고민해보게 되었다. 고민해보게 된 이유는 간단하다. 매번 장애가 나면 대부분 네트워크인데, 이 네트워크 문제를 더 복잡하게만 만들고, 스케일아웃을 하지 않는 온프레미스 서버이기 때문에 클라우드처럼 비용 최적화를 위해 오토스케일을 할 필요가 없기 때문이다.

결론은 금방 나왔다.

쿠버네티스를 사용하는 이유

쉽게 말해서 한 명의 인프라 관리자가 커버할 수 있는 서버를 늘리는 것이다(일자리 냠냠). 이전엔 각 서버를 따로따로 관리하면서 진행했겠지만, 쿠버네티스를 서버에 설치하고 노드를 엮는 순간 한 관리자가 API 호출을 통해 프로세스를 원하는 노드에 쉽게 올릴 수 있다. 심지어 인프라를 코드로서 관리할 수 있게 된다. 인프라 구성을 yaml파일로 만들어 두면, 그냥 kubectl apply -f filename.yaml 한 방에 구성된다. 코드로서 관리한다는 것은 라이브러리화가 가능하다는 것도 시사한다. 마치 npm처럼 사용할 수 있는 helm이 등장한다. 클라우드 세팅까지 함께 진행 가능한 Terraform도 등장한다. 꽤 큰 변화가 아닌가 싶다.

문제점

소프트웨어를 제품을 타사에 설치해주는 경우, 쿠버네티스가 여기에 포함된다면, 그리고 상주인력을 파견하지 않는 형태라면, 대참사가 벌어진다. 일단 겪어본 것만 나열해 보겠다.

  1. 서비스에 장애가 발생했는데, 다수의 Restart 후 etcd의 리소스 사용량이 치솟더니 호스트가 죽어버린 케이스가 발생했다. 문제를 전달받고 방문했을 땐 이미 디버깅조차 할 기회가 없었다. 디버깅을 위해 루트 권한이 필요함은 물론 기본이다.
  2. Calico CNI를 오랫 동안 켜두면 어느 순간 멈춘다. → 매일 서비스 가동과 함께 재시작한다.
  3. 로그 용량을 제한할 정책을 제대로 만들지 않고 서비스를 방치하면 거래량이 많거나 다른 문제가 있는 경우 터진다.
  4. 리눅스의 자동 업데이트 기능으로 인해 도커 데몬이 영향을 받으면 다 터진다. → 자동 업데이트를 해제한다.
  5. Kubeadm으로 설치할 때 발급된 API call 시 사용할 인증서의 유효기간은 1년이다. → 1년 마다 모든 고객사를 방문해서 루트 권한을 받아 인증서를 갱신해야 한다.
  6. 고객사의 쿠버네티스 버전을 업데이트하기 위해서는 루트 권한을 받아서, 현재 클러스터를 내리고, kubelet도 내리고, kubelet, kubeadm, kubectl 바이너리를 새 것으로 교체하고, 쿠버네티스의 새 버전 이미지들을 로드하여 kubeadm init만 하는 과정이면 정말 쉬울텐데... 1.18버전에서 1.21버전 올라갈 땐 몇몇 피쳐가 정식 피쳐로 바뀌면서 서비스의 yaml 파일들을 싹 업데이트해야 했고, 1.26버전을 올라갈 땐 도커 지원을 중단하는 큰 변화가 발생해서 containerd를 추가로 세팅해야 했다. 솔직히 또 1년 안에 무슨 큰 변화가 발생할 지 겁이 난다.

보다시피, 쿠버네티스는 인프라를 직접 관리하는 운영자가 사용하기 위한 것이지, 타사에 납품할 제품에 심는 그런 것이 아니다.

그럼 어떻게?

이미 내 눈 밖에 났다. 쿠버네티스를 날려버렸다. 내가 관리하는 마이크로서비스가 쿠버네티스에 요청을 날려서 모델을 로드할 워커의 개수와 모델에 등록할 작업의 개수를 관리하는 역할을 하는데, 여기서 쿠버네티스 환경인지 아닌지를 탐지해서 쿠버네티스가 아닌 경우 해당 피쳐를 off시키도록 바꿨다. 애초에 제품이 스케일아웃을 고려하여 stateless하게 개발되었기 때문에 다른 마이크로서비스들엔 딱히 이슈가 없었다. 그리고, 도커 이미지가 아닌, 바이너리로 그대로 가져다 쓰는 것도 문제 없도록 했다. 물론, 새 고객사엔 podman으로 서비스를 운영한다. 도커 이 살찐고래는 너무 많은 권한을 가지고 있다. 단지 포트 열고 통신하고 로직을 돌릴 프로그램을 적재하는데 루트권한은 투머치다. podman은 루트가 필요없다. 만만세. 아, 그리고 이후 시간이 날 땐 rpm, deb 패키징도 해볼 생각이다.

결과

편해졌다.

그런데, 뜬금없이 이 과정에서 엄청난 일이 발생했다. 사용하고 있는 메시지큐의 리소스 요구량이 확 줄어든 것이다. 기존엔 2000개 동시작업을 처리하기 위해 broker와 bookie를 3개씩 올리고 거기다 각각 3cpu를 할당하고, 워커에 4개를 할당하고 zookeeper, proxy까지 합하면 최소 25cpu를 할당해야 했는데, 그냥 standalone 이미지 하나로 올리니, 12-thread cpu 서버에서 2000개 잡을 비슷한 수준의 레이턴시를 보이며 돌려버렸다.

결국, 2.0의 흔적은 신규 학습에서 제외된 몇몇 모델을 제외하고, 대부분 사라졌다. 물론 사내에서 실장 레코드를 쌓기 위한 서버는 여전히 쿠버네티스를 이용 중이다. 실시간으로 관리하고 있으니까 말이다.

존버록

난 현 회사의 제품 1.0 버전이 성공하여 2.0 버전 개발을 완료한 후 설치를 앞두고 있던 시기에 입사했다. 2.0 버전은 B2B2C까지도 가능한 시스템을 목표로 하여, 고객사의 클라우드에 설치를 진행하는 IaaS 형태로 설계, 개발되었다.

그래서인지, 구조가 매우 복잡하였다.

먼저, 6개의 마이크로서비스로 서비스가 분리되었다. 그리고 각 서비스는 stateless한 구조로 개발되어, 쿠버네티스 클러스터에 디플로이되었다. 그리고, stateless한 서비스의 상태 복구를 위해 DB를 이용했는데, 복구만을 위해 이용하니, 그냥 팟으로 같이 올렸다. 마이크로서비스로서 동작하기 위해, 또한 작업 등의 상태 복구를 위해 persistence한 메시지큐도 필요했다. 그리고 이 메시지큐로 채택된 것은 Apache Pulsar. IaC도 이용했다. helm chart와 terraform을 구성한다. 로깅도 빠질 수 없다. Elasticsearch + Fluentbit + Kibana 스택을 이용한다. 장애대응도 해야 한다. Kibana에서 Cloudwatch 메트릭을 쏴서 고객사에 문제 알림이 가능하도록 세팅해 줬다.

아닛? 보안 컴플라이언스? 코드를 고객사 서버에서 빌드하는데 CI/CD로 해달라고? 고객사 jenkins에 세팅해 줬다. 파일 반출이 간단하지 않다. 그냥 남겨둠. 보안? 그러고보니...? 네, 방화벽 타이트하게 쪼아주세요. 정신없이 VPC와 subnet, 그리고 EKS, ES 등의 매니지드서비스의 엔드포인트들을 이어가면서 방화벽 작업도 진행했다. 보안은 끝이 없다. 서비스의 IP를 고정해주세요. 네? 쿠버네티스 클러스터의 IP를 고정하라고? nginx로 로드밸런서 켜서 어떻게든 고정해줌. 아니 또 이 로드밸런서 키니까 any allowed 방화벽 룰이 추가되네? 다시 쪼아줌.

...(후략)

눈치챘겠지만, 드러나는 한계 feat. 소규모 팀

하지만 갈수록 한계가 드러났다. 6개의 마이크로서비스, 메시지큐, 디플로이를 위한 IaC들, 고객사의 요청에 따른 자잘한 커스터마이징들... 그와중에 terraform은 신나게 만들어 놓고, 고객사에서 사용을 거부하여 결국 손으로 세팅해야 했다.

가장 문제는 인원이었다. 상기한 것들을 감당하는 사람의 수는 초기엔 6명, 퇴사와 충원을 거처 4명이 되었다. 보통 한 팀이 하나의 마이크로서비스를 맡는다고 알고 있는데, 이 팀은 한 명이 여러 개의 마이크로서비스를 관리해야 했다.

존버는 승리한다 feat. 3.0

관리포인트 줄이기

분산된 마이크로서비스 중 기능이 겹치는 서비스들을 합쳤다. 그 결과, 6개의 마이크로서비스는 3개로 줄었다. 또한, 필요없는 레이어를 날려버렸다. SI처럼 타사에 설치를 해주는 상태에서 IaC는 사치였다. 일단 나 말고 아무도 못알아봄. helm chart를 날리고 모두 kubectl로 바로 deploy 가능하게 yaml파일을 구성했다. terraform은 애초에 2.0 설치하면서 고객사가 거부하여 못쓰게 되어 구경도 안해봤으므로 패스. 그냥 갖다버렸다. 추가로, 모 증권사만 이용하는 클라우드는 해당 증권사 전용이므로, 온프레미스 환경을 표준으로 삼고 bare-metal 설치를 중심으로 관리하면서, 혹시 클라우드를 이용하는 경우 온프레미스 환경에서 필요한 부분만 바꿔서 이용하는 방향으로 변경하였다. 아 그리고, 클라우드를 이용하던 그 증권사도 결국 엄청난 비용에 경악하며 온프레미스로 전환하였다.

코드베이스 리뉴얼

마이크로서비스를 통합하면서 구조를 변경하는 김에, 담당하던 마이크로서비스를 완전 리뉴얼하였다. 당시 난 레이어드패턴에 대해 막 배워가던 시절이었다. 근데, 난 개념을 배웠을 때 아무렇게나 응용하는게 특기라서, 액터 구조랑 혼합했다. type, interface, actor, loop, util 이렇게 5가지 요소로 나누었고, actor는 데이터 계층과 연결되는 interface를 이용하는 역할과, 동기화 이슈가 있을 수 있는 리소스를 관리하는 역할을, loop에는 비즈니스 로직을 몰아넣고, util엔 길고 가독성 떨어지거나, 반복적인 로직을 함수로 빼다가 넣었다. type은 말그대로 type.

그 결과, 내가 짠 코드라서 그런건지, 아니면 비즈니스로직이 한 군데에 몰려있어서 그런지는 모르겠지만, 이전엔 1~2주 걸리던 피쳐들을 하루이틀에 쳐낼 수 있게 되었다.

설치 난이도 줄이기

고객사에 설치 작업을 할 때 진행되는 과정 중 가장 어려운 것은 쿠버네티스 설치이다. 인터넷이 안되는 환경으로 필요한 바이너리와 이미지를 한 번에 들고가서 진행해야 하기 때문이다. 그런데, 그동안은 이 준비 과정을 매뉴얼하게 진행하고 있었고, 무슨 비급같이 생긴 문서를 참고하며 진행하였다.

네 잘하고 있어요. 하지만 쿠버네티스는 버전업 주기가 빠르답니다? 하하하하ㅏ핳....

설치할 때마다 대작업이었다. 그래서 설치 준비과정을 자동화했다. 그 김에 그냥 인터넷 될 때 이용하는 설치 스크립트도 같이 만들어서, 사내에서 이용하는 테스트용 서버 구축도 쉽게 되도록 했다.

문제는 1.26버전부터 dockershim 지원 중단... 그래서 싹 다시 짰는데 내 개발PC와 CI 서버에만 세팅 성공하고 고객사 설치는 아직 실패해서 1.22버전에 머물러 있다... 하지만 이 버전은 공식지원 종료라고 알고 있어서, 조만간 업그레이드해야함.

존버 승리?

승리한 듯 하다. 난 한동안 편해졌다. 그리고 새 역할이 추가되었다. 어...?

아 싫진 않다. 원래 인프라쪽 담당이라 리서치와는 좀 멀었는데, 추가된 새 역할은 리서치의 학습과 연관된 일이라서, 뇌가 덜심심하다. 인프라는 이건 이런데 저건 저렇고 그래서 이걸 바꾸면 또 저게 어쩌고 하는 정신없는 와중에 정신줄 붙잡는 느낌인데, 이쪽은 문제푸는 느낌이다. 인프라 하다가 현타오면 리서치쪽 업무 건들다가 진빠지면 인프라 건드리고 하면 됨.

또한 서비스 안정성이 미친듯이 높아졌다. 원래 우리 서비스는 성능은 좋지만 그와 동시에 장애의 아이콘이었는데, 내심 앞으로가 좀 기대된다.

얻은 것

  • 서비스 안정성
  • 직무능력
  • 처우개선

잃은 것

  • 2.0을 유지하다가 번아웃되어 사라진 팀원들
  • 눈알의 수분
  • 건강

배운 점

  • Simple is the best
  • 유지보수를 신경써서 설계하자
  • 힙해보이면 일단 다시 한 번 생각해보자
  • 이력서 쓰기 좋은 테크스택은 이런거구나