쿠 버네 티스 를 이용해 테 스팅 환경 구현 해 보 기 - 우아 한 형제 들 기술 블로그 
 쿠 버네 티스 를 이용해 테 스팅 환경 구현 해 보 기 
 Mar 13 , 2018 • 장하윤 
 실제로 서비스 에 도입 해 보 기 전 에 쿠 버네 티스 를 유용 하 게 사용 해 볼 수 있 는 방법 중 에 하나 가 아닐까 . 
 이 글 은 앞 으로 우리 가 관리 하 는 서비스 ( 배 민찬 ) 의 아키텍처 가 컨테이너 기반 의 마이크 로 서비스 를 지향 할 것 으로 결정 한 후 이 를 위해 우선 아키텍처 를 테스 팅 환경 으로 구현 하 여 실제로 서비스 에 도입 하 기 전 에 충분 한 기간 을 가지 고 사용 경험 을 쌓 는 것 을 목적 으로 쿠 버네 티스 로 테스 팅 환경 을 구축 하 면서 겪 었 던 여러 상황 들 을 정리 해 본 글 이 다 . 쿠 버네 티스 의 구조 와 그 리소스 ( Pod , Service , Deployment , ETC … ) 등 은 그것 만 으로 도 책 한 권 은 가벼운 주제 라 생각 되 니 그 에 대한 설명 을 할 수 는 없 지만 전체 적 인 테 스팅 환경 의 구조 와 작업 을 진행 하 면서 막혔 던 부분 , 주의 해야 했 던 부분 을 짤막 하 게 언급 하 는 식 으로 작성 하 였 다 . 
 테스 팅 환경 을 클러스터 에 구현 하 기 
 AWS 환경 에 쿠 버네 티스 클러스터 ( 이하 클러스터 ) 올리 기 
 일단 쿠 버네 티스 리소스 를 올리 려면 클러스터 를 만들 어야 한다 . AWS 에서 클러스터 를 구축 을 하 는 데 에 는 kops 를 사용 하 였 다 . 공식 사이트 의 가이드 에 도 AWS 에서 클러스터 를 올리 는 방법 중 하나 로 설명 이 되 어 있 으며 비교 적 손쉽 게 클러스터 를 생성 할 수 있 기 때문 이 다 . kops 가 요구 하 는 AWS 권한 을 가진 계정 으로 awscli 를 사용 할 수 있 는 상황 이 라면 가이드 를 따라서 손쉽 게 클러스터 를 만들 어 낼 수 있 다 . 생성 할 클러스터 의 구조 는 다음 과 같 다 . 
 시작 은 작 은 규모 로 t 2 . medium 마스터 1 대 , 미니언 2 대 로 구성 하 도록 하 였 다 . 마스터 노드 의 수 는 Raft 알고리즘 의 특성 상 홀수 로 유지 하 는 것 이 좋 은데 두 개 의 마스터 노드 는 한 개 만 못한 결과 를 초래 할 수 있 기 때문 이 다 ( 둘 중 하나 만 다운 돼도 둘 다 다운 되 는 효과 가 나오 는 기적 ) . 마스터 , 미니언 노드 는 private 서브 넷 에 생성 되 며 private 서브 넷 에 있 는 노드 ( EC 2 인 스턴스 ) 에 접속 하 려면 Bastion 노드 를 통해야 한다 . 위 의 구조 를 가지 는 클러스터 를 생성 하 는 명령어 는 다음 과 같 다 . 자세 한 파라미터 목록 은 kops create cluster 문서 를 참조 하 자 . 
 -- zones ap - northeast - 2 a , ap - northeast - 2 c \ 
 -- master - zones ap - northeast - 2 a \ 
 참고 로 kops 가이드 를 그대로 따라 할 경우 노드 사이즈 에 대한 파라미터 를 정의 하 지 않 으므로 t 2 . large 타입 과 같 은 유료 인 스턴스 로 노드 가 생성 되 어 요금 이 부과 될 것 이 다 . 생성 되 는 인 스턴스 의 타입 은 파라미터 로 지정 할 수 있 으니 kops update cluster -- yes 명령어 를 실행 하 기 전 에 꼭 확인 하 기 바란다 . 그리고 클러스터 를 생성 했 다면 AWS 에 생성 된 인 스턴스 를 확인 하 여 회사 든 개인 이 든 예산 에 차질 이 없 도록 하 는 게 좋 을 것 이 다 . 
 Public Subnet 에 바스티 온 노드 만 올라간다고 CIDR 범위 를 좁 게 잡 으면 안 된다 . 나중 에 쿠 버네 티스 의 로드 밸 런 서 서비스 ( 이하 로드 밸 런 서 , AWS 의 ELB 는 ELB 라고 표기 하 겠 다 . ) 를 만들 때 Utility 타입 의 서브 넷 ( 하단 의 코드 블록 에 있 음 ) 에서 사용 가능 한 아이피 의 수 가 8 개 이상 이 어야 ELB 가 생성 되 어 로드 밸 런 서 가 올바르 게 동작 한다 . 
 클러스터 를 만드 는 것 은 가이드 대로 하 면 간단 한 일 이 지만 클러스터 를 생성 하 면 클러스터 에 사용 되 는 VPC 가 생성 되 는데 , 다른 VPC 와 의 피어 링 등 의 이슈 가 있 어 VPC 와 서브 넷 의 CIDR 값 을 수동 으로 지정 해야 한다면 kops update cluster -- yes 명령어 를 사용 하 여 최종 적 으로 클러스터 를 생성 하 기 전 에 kops edit cluster 명령어 를 사용 하 면 에디터 가 열리 고 VPC 와 Subnet 의 CIDR 을 수정 할 수 있 다 . 이미 클러스터 를 생성 했 는데 이 를 변경 해야 한다면 차라리 kops delete cluster 명령어 로 클러스터 자체 를 삭제 하 고 새로 만드 는 게 더 빠르 다 . 
 networkCIDR : 10 . xx . xx . xx / 16 . # VPC 의 CIDR 
 - cidr : 10 . xx . xx . xx / 24 
 name : ap - northeast - 2 a 
 type : Private # Private 는 Private 서브 넷 을 의미 한다 . 
 zone : ap - northeast - 2 a 
 name : ap - northeast - 2 c 
 zone : ap - northeast - 2 c 
 name : utility - ap - northeast - 2 a 
 type : Utility # Utility 는 Public Subnet 을 의미 한다 . 
 name : utility - ap - northeast - 2 c 
 수정 내역 을 저장 한 후 kops update cluster -- yes 명령어 를 사용 하 여 클러스터 를 생성 했 다면 이제 Pod ( 이하 팟 ) 에 사용 할 도커 이미지 를 준비 한다 . 
 클러스터 환경 이 준비 되 었 다면 Pod 에 사용 될 도커 이미지 를 만들 어야 한다 . 테스 팅 환경 을 구성 하 기 위해선 4 개 의 컨테이너 가 필요 했 는데 , 이 컨테이너 들 은 다음 과 같 은 관계 를 지닌다 . 
 필자 는 로컬 에서 도커 이미지 를 빌 드 하 여 ECR 에 푸 쉬 한 후 클러스터 에서 사용 하 였 다 . kops 를 통해 AWS 상 에 클러스터 를 만들 었 다면 동일 한 계정 의 ECR 에 올린 도커 이미지 는 별도 의 imagePullSecret 이 없 어도 가져다 쓸 수 있 다 . 
 imagePullSecret 은 쿠 버네 티스 에서 private docker registry 에 있 는 도커 이미지 를 사용 하 기 위한 토큰 같 은 것 이 라고 생각 하 면 될 것 같 다 . 만약 AWS 에 있 는 클러스터 에 올리 기 전 에 로컬 에서 minikube 등 을 이용 하 여 ECR 에 있 는 이미지 를 테스트 하 려면 imagePullSecret 을 설정 하 지 않 고 는 해당 이미지 를 가져와 사용 할 수 없 다 . ImagePullSecret 의 생 성 과 사용법 은 이곳 에서 확인 할 수 있 다 . 
 minikube 에서 테스트 를 할 때 는 minikube 에서 이미지 를 빌 드 해 버리 면 된다 . 본인 은 minikube 의 도커 이미지 를 사용 하 려면 어딘가 접속 가능 한 docker registry 에 이미지 가 올라가 있 어야 한다고 착각 하 였 는데 minikube ssh 명령어 를 사용 하 면 minikube 머신 에 접속 할 수 있 고 , 접속 하 면 docker - machine 에 접속 한 것 과 유사 함 을 알 수 있 다 ( 실제로 별다른 설정 없이 설치 하 면 docker - machine 처럼 VirtualBox 에 minikube 머신 이 추가 된 것 을 볼 수 있 다 . ). Mac 이 라면 minikube 에서 / Users 경로 를 통해 로컬 파일 시스템 에 접근 할 수 있 다 . 
 쿠 버네 티스 리소스 만들 기 
 이미지 가 준비 되 었 다면 이제 쿠 버네 티스 리소스 를 만든 다 . 다음 과 같 은 구조 로 구현 하 였 다 . 참고 로 그림 에 있 는 팟 들 은 전부 Deployment 리소스 가 관리 하 고 있 다고 생각 하 면 된다 . 
 nginx 라우터 팟 이 로드 밸 런 서 와 연결 되 어 있 으며 ELB 로부터 요청 을 받 아 각 서비스 로 요청 을 전달 하 도록 되 어 있 는 테 스팅 환경 의 구조 . 
 테스트 를 구성 하 는 팟 에 는 한 개 의 컨테이너 만 배포 되 어 있 지만 팟 에 는 여러 개 의 컨테이너 를 한 번 에 배포 하 여 사용 할 수 있 다 . 나중 에 젠킨스 를 통한 빌드 작업 에서 하나 의 팟 에 여러 컨테이너 를 실행 하 여 사용 하 는 것 을 볼 수 있 을 것 이 다 . 
 필자 가 구현 할 테 스팅 환경 은 https 프로토콜 을 사용 하 기 위해 로드 밸 런 서가 생성 하 는 ELB 가 AWS ACM 인증서 를 사용 하 도록 해야 했 는데 , 이것 은 로드 밸 런 서 설정 에 다음 어 노테이션 을 추가 하 여 해결 하 였 다 . 해당 어 노테이션 을 추가 하 면 AWS 콘솔 에서 ACM 이 ELB 에 사용 되 는 것 을 확인 할 수 있 을 것 이 다 . 
 service . beta . kubernetes . io / aws - load - balancer - ssl - cert : ACM 의 ARN 
 테스 팅 환경 을 구성 할 쿠 버네 티스 리소스 의 yaml 설정 파일 들 이 파일 이 준비 되 었 다면 kubectrl create - f 명령어 를 통해 쿠 버네 티스 리소스 들 을 클러스터 에 모두 생성 하 고 테스트 를 해 본다 . 감사 하 게 도 QA 팀 분 들 이 테스트 를 해 주 셔서 테스 팅 환경 이 구현 되 었 음 을 확인 할 수 있 었 다 . 
 진정한 고난 의 시작 , 배포 자동 화 
 이제 쿠 버네 티스 클러스터 에 테스 팅 환경 이 올라갔 다 . 이제 이 테스 팅 환경 에서 테스트 를 진행할 수 있 을 것 같 은데 , 현재 는 하나 의 테스 팅 환경 을 올렸 을 뿐 이 다 . 그리고 현재 는 테스 팅 환경 을 하나 만드 는 것 만 해도 다음 과 같 은 절차 가 필요 하 다 . 
 테스트 를 하 고자 하 는 소스 를 Git 에서 체크아웃 
 빌드 ( Java 의 gradle , PHP 의 Composer , node 이 npm 등등 ) 
 도커 이미지 빌드 , 푸쉬 
 쿠 버네 티스 리소스 생성 
 만약 이것 을 자동 화 하 지 않 으면 클러스터 에 테스 팅 환경 을 매번 배포 하 는 것 부터 가 큰일 이 니 젠킨스 를 사용 하 여 자동 화 를 해야 할 것 같 다 . 이 과정 을 젠킨스 에서 처리 하 면 다음 과 같 은 절차 를 거치 게 될 것 이 다 . 
 그런데 막상 배포 자동화 를 하 려고 하 니 문제 가 있 었 다 . 여러 명 이 각자 의 테스 팅 환경 을 사용 할 수 있 도록 각 테스 팅 환경 은 alpha - cluster 01 ~ 05 - www . testdomain . com 와 같 은 서로 다른 도메인 을 사용 해야 했 는데 , 이 를 위해서 는 쿠 버네 티스 리소스 와 함께 nginx 의 설정 파일 , Java 의 환경 변수 , PHP 설정 파일 을 변경 해야 했 고 이 를 젠킨스 에서 처리 하 도록 하 는 것 은 매우 비 효율 적 이 었 기 때문 이 다 . 
 Helm 패키지 매니저 
 Helm 은 쿠 버네 티스 패키지 매니저 인데 , helm 사용 하 면 클러스터 에 Tiller 라는 팟 이 설치 되 고 , 이 팟 을 통해 Helm 패키지 ( 이하 차트 ) 내부 에 정의 한 쿠 버네 티스 리소스 들 을 클러스터 에 올릴 수 있 다 . 흥미 로운 점 은 Helm 에서 템플 릿 기능 을 지원 한다는 것 이 다 . 이 템플 릿 기능 을 사용 하 여 도메인 에 따라 설정 을 변경 해 줘야 하 는 작업 을 해결 할 수 있 었 다 . 
 Helm 템플 릿 예시 - front web 서비스 의 configmap . yaml 
 configuration . ini : | 
 config . php : | 
 이때 -- name 으로 넘기 는 파라미터 는 생성 된 테 스팅 환경 에 접속 할 때 사용 할 도메인 의 접두사 ( ex : alpha - cluster 01 ~ 05 ) 를 사용 하 는데 이 는 alpha - cluster 01 - www . testdomain . com 와 같 은 도메인 설정 을 동적 으로 하 고 차트 를 통해 배포 된 테 스팅 환경 에서 생성 된 ClusterIP 서비스 를 각 테스 팅 환경 끼리 구분 하 기 위해서 사용 된다 . 
 front web 서비스 의 deployment . yaml 
 name : front - web 
 mountPath : / home / abc / def / configuration . ini 
 subPath : configuration . ini 
 mountPath : / home / abc / def / config . php 
 그리고 helm 에서 는 차트 의 의존 성 을 관리 할 수 있 는데 , 이 기능 을 사용 하 면 테스 팅 환경 에 필요 한 각 서비스 별 로 차트 를 일일이 배포 하 지 않 고 , 이 들 을 포함 하 는 하나 의 임의 의 패키지 를 만들 어 한 번 에 배포 할 수 있 다 ( 사실 차트 를 동일 한 이름 으로 중복 해서 배포 할 수 없 으므로 테스 팅 환경 을 구성 하 는 서비스 들 의 차트 들 이 동일 한 도메인 을 사용 하 려면 하나 의 차트 로써 배포 될 필요 도 있 었 다 ) . helm 차트 로 구성 한 테 스팅 환경 의 구조 는 다음 과 같 다 . 
 이제 helm install 명령어 로 alpha 차트 를 배포 할 때 -- name 에 설정 한 도메인 접두사 가 각 서비스 의 컨 피그 맵 에 치환 되 어 들어갈 것 이 다 . 차트 가 준비 되 었 으면 helm install 명령어 를 통해 클러스터 에 차트 를 배포 하 는 것 이 가능 하 다 . helm install 명령어 에서 는 템플 릿 에서 사용 할 값 을 파라미터 로 넘겨 줄 수 도 있 는데 , 이 기능 은 잠시 후 에 jenkins 파이프라인 예시 에서 helm 차트 를 배포 할 때 파이프라인 을 통해 빌드 된 도커 이미지 의 태그 를 제공 하 는 용도 로 사용 하 였 다 . 
 helm 차트 를 만듦으로써 테스 팅 환경 의 배포 는 helm install 명령어 하나 로 처리 할 수 있 게 되 었 고 도메인 변경 에 따른 문제 도 해결 되 었 다 . 그럼 이제 배포 자동화 를 위해 젠킨스 파이프 라인 을 구성 해 보 도록 하 겠 다 . 
 Helm 젠킨스 패키지 
 helm 을 이용 하 면 helm 차트 저 장소 에 등록 되 어 있 는 차트 들 을 내려 받 아 사용 할 수 있 는데 이 중 에 는 젠킨스 차트 도 있 으며 이 차트 는 이미 쿠 버네 티스 클러스터 관련 설정 이 이미 되 어 있 어 매우 유용 하 다 . ( 쿠 버네 티스 젠킨스 플러그인 이 이미 설치 되 어 있 고 해당 젠킨스 차트 가 올라가 있 는 클러스터 에 관한 설정 이 이미 되 어 있 는 상태 ) , 그리고 설치 되 면서 자동 으로 PersistentVolume ( 이하 PV ) 리소스 를 생성 하 여 젠킨스 관련 데이터 를 영속 적 으로 저장 하 기 때문 에 노드 자체 가 날아가 는 상황 에서 도 별도 의 설정 없이 젠킨스 의 데이터 를 유지 할 수 있 는 장점 이 있 다 . 
 젠킨스 차트 를 클러스터 에 배포 했 을 때 의 구조 
 참고 로 본인 은 이 패키지 의 존재 를 모르 고 오피셜 젠킨스 이미지 를 사용 하 던 중 노드 가 다운 되 는 일 이 벌어졌 는데 PV 을 사용 하 지 않 아 거의 다 완성 한 파이프라인 을 날려 먹 는 참사 를 당하 게 되 었 다 . 별도 로 PV 를 마운트 하 거나 hostPath 를 마운트 한 경로 에 데이터 를 저장 하 지 않 으면 팟 의 삭제 와 함께 모든 데이터 가 날아가 니 팟 에서 만들 어 지 는 소중 한 데이터 는 꼭 영속 적 인 저 장소 에 저장 하 도록 하 자 . 
 그리고 젠킨스 의 쿠 버네 티스 플러그인 은 젠킨스 슬레이브 팟 을 클러스터 에 배포 하 여 빌드 를 지원 하 는데 , 흥미 롭 게 도 이 슬레이브 팟 에 는 여러 컨테이너 를 추가 할 수 있 어 이 를 빌드 과정 에서 사용 할 수 있 다 . 그렇 다면 굳이 마스터 젠킨스 에 빌드 를 위한 기능 을 설치 하 지 않 고 빌드 전용 컨테이너 를 만들 어 빌드 작업 자체 의 휴대 성 을 높일 수 도 있 을 것 이 다 . 이 는 php , composer , npm 등 빌드 에 필요 한 게 많 았 던 레거시 웹 서비스 빌드 에 특히 유용 했 다 . 
 프론트 웹 빌드 용 젠킨스 슬레이브 팟 
 그리고 젠킨스 슬레이브 팟 에서 kubectl , helm 컨테이너 를 사용 시 에 는 별도 의 설정 없이 클러스터 를 사용 할 수 있 어 kubectl 의 config 파일 을 설정 할 필요 도 없 어 진다 . 이제 이 를 사용 하 는 파이프라인 을 작성 해 보 자 . 
 프론트 웹 을 빌 드 하 는 파이프라인 
 def frontWebImageTag 
 podTemplate ( label : ' build - front - web ' 
 containerTemplate ( name : ' docker ', image : ' docker : 17 . 11 ', ttyEnabled : true , command : ' cat ', resourceLimitMemory : ' 64 Mi '), 
 / / 별도 로 container 문 을 사용 하 지 않 으면 Jenkins 슬레이브 컨테이너 에서 명령어 가 실행 된다 . 
 다른 자바 서비스 들 도 마찬가지 로 빌드 → 도커 이미지 빌드 → 도커 이미지 푸쉬 의 동일 한 과정 을 거친다 . 필요 한 도커 이미지 를 모두 빌드 하고 푸 쉬 했 다면 helm 커맨드 를 실행 하 며 helm 차트 를 배포 한다 . 
 . / / 도커 이미지 빌드 & 푸쉬 
 podTemplate ( label : ' helm ' 
 containerTemplate ( name : ' helm ', image : ' lachlanevenson / k 8 s - helm : latest ', ttyEnabled : true , command : ' cat ') 
 sh " helm init " / / helm init 명령어 는 클러스터 에 Tiller 를 배포 하 면서 로컬 환경 에서 사용 하 는 차트 저 장소 를 만든다 . 
 sh " helm package helm / alpha / router " # 패 키 징 된 차트 는 로컬 차트 저 장소 에 저장 된다 . 
 sh " helm package helm / alpha / front - web " 
 sh " helm package helm / alpha / msa 1 " 
 sh " helm package helm / alpha / api - gateway " 
 sh " helm dependency update helm / alpha / alpha " # 의존 하 는 모든 패키지 를 가져와 알파 패키지 에 추가 한다 . 위 에서 패 키 징한 차트 는 로컬 저 장소 에 저장 되 어 있 어 가져올 수 있 다 . 
 / / 도커 이미지 의 태그 값 은 중복 되 지 않 도록 git commit ID 와 Jenkins 빌드 넘버 의 조합 이 다 . 이 값 은 helm install 명령 에 파라미터 로써 제공 되 어 젠킨스 파이프라인 에서 빌드 한 이미지 를 팟 에서 사용 하 는 것 이 가능 하 게 해준다 . 
 빌드 를 실행 했 는데 빌드 가 계속 대기 중 이 고 시작 되 지 않 는다면 젠킨스 설정 에서 Executor 설정 을 확인 하 자 . 필자 의 경우 젠킨스 차트 를 통해 설치 했 을 때 기본 값 이 0 으로 저장 되 어 있 어 영문 도 모르 고 한동안 기약 없 는 대기 를 타 야 했 다 . 
 차트 배포 결과 
 NAME 	 REVISION 	 UPDATED 	 STATUS 	 CHART 	 NAMESPACE 
 alpha - cluster 01 	 1 	 Sat DEC 9 14 : 36 : 53 2017 	 DEPLOYED 	 alpha - 1 . 0 . 0 	 default 
 jenkins 	 1 	 Thu Dec 14 15 : 29 : 29 2017 	 DEPLOYED 	 jenkins - 0 . 10 . 1 	 jenkins 
 alpha - cluster 01 - api - gateway - 4117715186 - f 23 mv 1 / 1 Running 0 1 m 
 alpha - cluster 01 - front - web - 1457034248 - qvnsr 1 / 1 Running 0 1 m 
 alpha - cluster 01 - msa 1 - 3928334195 - 11092 1 / 1 Running 0 1 m 
 alpha - cluster 01 - router - 1217976428 - 8 xp 7 g 1 / 1 Running 0 1 m 
 / /-- name alpha - cluster 01 값 으로 설치 된 차트 의 서비스 들 은 alpha - cluster 01 접두사 를 가지 게 되 어 다른 테스 팅 환경 의 서비스 와 간편 하 게 구분 되 고 사용 될 수 있 다 . 
 alpha - cluster 01 - api - gateway 100 . 68 . 48 . 102 < none > 80 / TCP 1 m 
 alpha - cluster 01 - api - proxy 100 . 64 . 121 . 224 < none > 80 / TCP , 443 / TCP 1 m 
 alpha - cluster 01 - front - web 100 . 66 . 0 . 18 < none > 80 / TCP , 443 / TCP 1 m 
 alpha - cluster 01 - msa 1 100 . 68 . 79 . 194 < none > 80 / TCP 1 m 
 alpha - cluster 01 - router 100 . 66 . 130 . 177 < none > 80 / TCP , 443 / TCP 1 m 
 이것 으로 배포 과정 이 자동 화 되 었 다 . 이제 차트 가 배포 되 었 을 때 생성 된 Router 로드 밸 런 서의 ELB 에 Route 53 을 통해 도메인 을 연결 해 주 기 만 하 면 테스 팅 환경 을 사용 할 수 있 게 되 었 다 . 그럼 이제 클러스터 가 기존 의 5 개 의 테스 팅 환경 ( 기존 에 테스 팅 환경 을 5 개 까지 사용 했으므로 ) 을 감당 해낼 수 있 는지 확인 해 보 자 . 
 노드 의 리소스 사용량 관리 
 안타깝 게 도 다수 의 테스 팅 환경 을 배포 하 는 과정 에서 미니언 노드 하나 가 다운 되 었 는데 , 원인 을 알아본 결과 팟 의 리소스 사용량 에 제한 을 두 지 않 아서 이런 문제 가 발생 한 것 으로 보인다 . 특히 CPU 보다 메모리 사용량 을 제한 하 지 않 을 경우 치명 적 인데 최악 의 경우 OOM ( Out of Memory ) 이 발생 하 여 노드 가 재 시작 될 수 도 있 다 . 팟 의 리소스 제한 을 설정 하 면 노드 에 충분 한 리소스 가 남 아 있 지 않 을 경우 팟 이 아예 배포 되 지 않 으므로 노드 의 OOM 을 방지 할 수 있 을 것 이 다 . 일단 팟 의 리소스 제한 을 설정 하 기 위해 어떤 팟 이 얼마나 리소스 를 사용 하 는지 모니터링 을 해 보 자 
 Heapster addon 설치 
 Heapster 를 설치 하 면 kubectl top ( node | pod ) 명령어 를 사용 하 여 각 노드 와 팟 의 CPU , 메모리 사용량 을 모니터링 할 수 있 다 . Heapster 만 설치 해서 는 명령어 를 친 순간 의 리소스 사용량 만 모니터링 할 수 있 지만 지금 은 이걸로 충분 한 것 같 다 ( influxDB , Grafana 플러그인 을 사용 하 면 좀 더 상세 한 모니터링 이 가능 하 다 ) . 그럼 명령어 를 한번 실행 해 보 자 . 
 ip - 10 - xx - xx - xx . ap - northeast - 2 . compute . internal 97 m 4 % 2642 Mi 68 % 
 ip - 10 - xx - xx - xx . ap - northeast - 2 . compute . internal 68 m 3 % 3678 Mi 46 % 
 ip - 10 - xx - xx - xx . ap - northeast - 2 . compute . internal 54 m 2 % 3339 Mi 42 % 
 $ kubectl top po 
 NAME CPU ( cores ) MEMORY ( bytes ) 
 alpha - cluster 01 - router - 1217976428 - 8 xp 7 g 0 m 10 Mi 
 alpha - cluster 01 - front - web - 3938765134 - hvxwr 0 m 124 Mi 
 alpha - cluster 01 - api - gateway - 4117715186 - f 23 mv 0 m 630 Mi 
 alpha - cluster 01 - msa 1 - 3928334195 - 11092 0 m 726 Mi 
 $ kubectl top po - n jenkins / / 젠킨스 와 젠킨스 슬레이브 팟 은 jenkins 네임 스페이스 를 가진다 . 
 jenkins - jenkins - 322528892 - n 58 cn 0 m 1143 Mi 
 jenkins - slave - 3928334195 - 12824 0 m 938 Mi / / 배포 중 일 때 kubectl top 명령어 를 사용 해 보 았 다 . 
 kubectl top pod 명령어 로 팟 의 리소스 를 모니터 할 때 는 팟 이 클러스터 에 올라가 고 시간 이 좀 지나 야 한다 . 금방 올린 팟 의 리소스 사용량 이 안 뜬다면 잠시 후 에 명령어 를 다시 실행 해 보 자 . 
 모니터링 결과 java 로 만든 msa 1 과 Api - gateway 팟 의 메모리 사용량 이 상당 함 을 알 수 있 었 다 . t 2 . medium 타입 의 EC 2 인 스턴스 를 두 미니언 노드 의 가용 메모리 는 8 Gi 인데 자바 를 사용 하 는 팟 두 개 만 으로 도 1 Gi 가 넘 는 메모리 를 사용 하 는 셈 이 다 . 배포 에 사용 되 는 젠킨스 팟 은 한술 더 뜨 는데 , 두 개 의 팟 이 거의 2 Gi 의 메모리 를 사용 하 고 있 는 상황 이 다 . 이런 상황 에서 메모리 사용량 을 제한 하 지 않 았 으니 팟 은 약간 의 노드 에 약간 의 리소스 만 사용 가능 해도 배포 되 었 을 것 이 며 시간 이 지남 에 따라 제한 없이 사용 메모리 를 계속 늘려 감 으로써 노드 의 OOM 을 초래 하 게 되 는 것 도 무리 가 아니 었 다 . 안정 적 으로 다수 의 테스 팅 환경 을 위해 팟 에 리소스 제한 을 설정 하 자 . 
 팟 의 리소스 제한 
 쿠 버네 티스 에선 팟 에서 는 사용 할 리소스 를 제한 할 수 있 는데 , 만약 제한 하 지 않 으면 팟 은 메모리 를 제한 없이 사용 하 게 된다 . 팟 이 사용 할 리소스 설정 에 는 request , limit 두 가지 가 있 는데 request 는 해당 팟 이 실행 되 기 위해 요구 하 는 리소스 사용량 으로 쿠 버네 티스 는 노드 에 request 에 설정 한 값 이상 의 리소스 가 남 아 있 을 경우 에 만 팟 을 노드 에 배포 한다 . limit 는 팟 이 최대 로 사용 할 수 있 는 리소스 의 값 이 다 . 
 리소스 사용량 을 설정 한 팟 이 노드 에 배포 될 때 
 노드 에 해당 팟 이 배포 될 수 있 는지 는 request 로 판단 함 에 주의 해야 한다 . 1 Gi 의 메모리 를 가진 노드 에 request 100 Mi , limit 1 Gi 로 설정 되 어 있 는 팟 을 두 개 배포 할 경우 두 팟 모두 배포 되 지만 각 팟 이 1 Gi 의 메모리 를 사용 하 려고 하 며 이 는 노드 에 OOM 을 발생 시킬 가능 성 이 있 다 . 
 Docker 에서 도 발생 하 는 문제 지만 컨테이너 에서 java 를 사용 할 때 JVM 의 힙 메모리 를 설정 하 지 않 으면 JVM 은 컨테이너 에서 사용 가능 한 메모리 가 아닌 노드 가 사용 할 수 있 는 총 메모리 를 기준 으로 힙 메모리 를 사용 할 것 이 다 . java 컨테이너 를 실행 할 때 는 힙 메모리 설정 을 하 는 것 이 안 전할 것 이 다 . 
 Deployment 에서 팟 리소스 제한 걸 기 
 - - server 
 - - Xmx 256 m 
 - - Xms 256 m 
 - - jar 
 - / home / java / app . jar 
 image : msa 1 - image - repo : msa 1 - image - tag 
 name : msa 1 
 memory : 448 Mi 
 Deployment 에서 관리 하 는 팟 의 리소스 제한 을 걸 어 준다 . 리소스 사용량 을 모니터링 을 하 면서 서비스 가 유지 되 는 한도 내 에서 최대한 줄여 보 았 다 . 다른 팟 들 의 리소스 사용량 도 설정 했 다면 . 이제 배포 에 사용 되 는 젠킨스 슬레이브 팟 의 리소스 도 제한 해야 하 는데 마찬가지 로 다음 과 같이 컨테이너 별 로 리소스 사용량 을 제한 할 수 있 다 . 
 podTemplate ( label : ' build - msa 1 ', 
 / / 젠킨스 슬레이브 컨테이너 는 선언 하 지 않 아도 자동 으로 포함 되 지만 리소스 제한 을 설정 하 기 위해서 는 직접 선언 해 줘야 한다 . 
 envVar ( key : ' JVM _ HEAP _ MIN ', value : '- Xmx 192 m '), 
 envVar ( key : ' JVM _ HEAP _ MAX ', value : '- Xmx 192 m ') 
 containerTemplate ( name : ' java ', image : ' java : 8 - jdk ', ttyEnabled : true , command : ' cat ', resourceLimitMemory : ' 768 Mi ', 
 envVar ( key : ' JVM _ HEAP _ MIN ', value : '- Xmx 256 m '), 
 envVar ( key : ' JVM _ HEAP _ MAX ', value : '- Xmx 256 m ') 
 이제 노드 가 OOM 으로 다운 되 는 문제 가 방지 되 는지 다시 테스 팅 환경 을 5 개 이상 배포 해 보 니 클러스터 가 매우 느려 지 고 마지막 에 배포 하 려는 테 스팅 환경 이 배포 되 지 않 는 상황 이 발생 하 긴 하 지만 노드 자체 가 다운 되 는 현상 은 발생 하 지 않 았 다 . 이제 나름 안정 을 찾 게 된 듯 하 다 . 그런데 리소스 사용량 을 설정 하 다 보 니 젠킨스 슬레이브 팟 의 메모리 사용량 워낙 많 음 ( 1 Gi 정도 를 제공 하 지 않 으면 제대로 동작 하 지 않 았 다 ) 을 알 게 되 었 는데 , 만약 두 개 의 노드 에 테스 팅 환경 을 위한 팟 이 골고루 배포 된다면 어느 노드 에 도 젠킨스 슬레이브 팟 이 배포 되 지 않 을 가능 성 이 있 지 않 을까 ? 
 리소스 는 남 는데 왜 배포 를 하 질 못하 니 … 
 사용 할 수 있 는 총 자원 은 충분 하 지만 팟 이 어떻게 배포 되 느냐 에 따라 자원 소모 가 큰 팟 이 배포 가 되 지 못할 수 있 다 . 그렇 다면 배포 를 할 때 젠킨스 슬레이브 팟 의 배포 를 위한 자원 을 확보 하 려면 두 개 의 노드 에 어떤 식 으로 팟 이 배포 될지 제어 를 할 필요 가 있 다 . 
 NodeAffinity & NodeSelector 
 팟 에 NodeAffinity 를 설정 하 면 팟 이 배포 될 때 어떤 노드 를 선호 할지 노드 에 붙 은 라벨 을 사용 하 여 설정 할 수 있 는데 , 노드 에 라벨 을 붙이 는 방법 은 다음 과 같 다 . 
 이미 붙 은 라벨 을 수정 하 려면 – overwrite 옵션 을 사용 해야 한다 . 
 노드 에 라벨 이 붙 은 상태 
 NAME STATUS AGE VERSION LABELS 
 Deployment 에서 관리 하 는 팟 에 NodeAffinity 설정 
 - key : alpha - pod - affinity 
 weight : 99 # 좀 극단 적 이 지만 alpha - pod - affinity 라벨 의 값 이 service 인 노드 를 편애 하 게 만들 도록 하 자 . 
 - deploy # alpha - pod - affinity 라벨 의 값 이 deploy 라면 service 노드 에 배포 불 가능 한 상황 일 경우 에 나 deploy 노드 에 배포 될 것 이 다 . 
 배포 를 위한 젠킨스 슬레이브 팟 에 도 NodeAffinity 를 적용 하 고 싶 었 으나 안타깝 게 도 지금 은 nodeAffinity 설정 을 지원 하 지 않 는 것 같 다 . 하지만 다행 스럽 게 도 nodeSelector 는 지원 하 는데 , nodeSelector 역시 노드 에 붙 은 라벨 을 이용 하 여 팟 의 배포 를 제어 할 수 있 지만 nodeAffinity 처럼 선호 하 는 노드 를 지정 하 는 것 이 아니 라 해당 라벨 을 가진 노드 에 만 팟 의 배포 가 가능 하 도록 하 는 방식 이 다 . 즉 nodeSelector 에 지정 한 라벨 을 가진 노드 가 없 을 때 는 팟 이 배포 되 지 못하 니 주의 하 자 . 
 파이프라인 에 nodeSelector 설정 을 추가 예시 
 팟 이 어떤 노드 에 배포 되 었 는지 확인 해 보 자 . 
 NAME READY STATUS RESTARTS AGE IP NODE 
 alpha - cluster 01 - api - gateway - 4117715186 - f 23 mv 1 / 1 Running 0 1 h 100 . 113 . 255 . 189 ip - 10 - xx - xx - xx . ap - northeast - 2 . compute . internal 
 alpha - cluster 01 - front - web - 3938765134 - hvxwr 1 / 1 Running 0 1 h 100 . 113 . 255 . 149 ip - 10 - xx - xx - xx . ap - northeast - 2 . compute . internal 
 alpha - cluster 01 - msa 1 - 3928334195 - 11092 1 / 1 Running 0 1 h 100 . 113 . 255 . 148 ip - 10 - xx - xx - xx . ap - northeast - 2 . compute . internal 
 alpha - cluster 01 - router - 1217976428 - 8 xp 7 g 1 / 1 Running 0 1 h 100 . 113 . 255 . 187 ip - 10 - xx - xx - xx . ap - northeast - 2 . compute . internal 
 $ kubectl get po - o wide - n jenkins 
 jenkins - jenkins - 322528892 - n 58 cn 1 / 1 Running 0 7 d 100 . 111 . 50 . 79 ip - 10 - yy - yy - yy . ap - northeast - 2 . compute . internal 
 jenkins - slave - 4028334195 - 12824 4 / 4 Running 0 2 m 100 . 111 . 50 . 82 ip - 10 - yy - yy - yy . ap - northeast - 2 . compute . internal 
 배포 속도 개선 
 클러스터 가 안정 되 니 이번 엔 15 분 을 넘어가 는 배포 속도 가 거슬리 기 시작 했 다 . 만약 이렇게 느린 배포 과정 에서 마지막 도커 이미지 빌드 에 사용 되 는 Git 브랜치 명 이라도 잘못 입력 하 여 빌드 가 실패 한다면 썩 유쾌 하 진 않 을 것 이 다 . 젠킨스 빌드 로그 를 보 면 빌드 에 걸리 는 속도 의 주범 은 gradle , composer , npm 등 을 사용 하 여 라이브러리 의존 성 을 처리 하 는 작업 인 것 으로 보이 는데 이런 작업 들 은 일반 적 인 상황 이 라면 실행 시 캐시 가 남 아 다음 실행 시 의 실행 시간 이 단축 되 지만 현재 빌드 환경 에서 는 언제나 새로운 젠킨스 슬레이브 팟 이 배포 되 기 때문 에 캐시 가 남 지 않 는 것 이 엄청나 게 느린 속도 빌드 속도 의 주범 이 다 . 그렇 다면 helm 의 jenkins 패키지 처럼 PV 를 마운트 하 여 캐시 를 영속 적 으로 저장 한다면 빌드 속도 를 개선 시킬 수 있 지 않 을까 ? 일단 AWS 에서 사용 할 EBS 를 만들 고 해당 id 를 이용 하 여 클러스터 에서 사용 할 PV 를 만들 도록 하 자 . 
 젠킨스 슬레이브 팟 에 캐 시용 EBS 를 PV ( Persistent Volume ) , PVC ( Persistent Volume Claim ) 을 사용 하 여 마운트 한 구조 도 
 PV 와 PVC 리소스 를 만들 었 다면 파이프라인 에 persistent volume 을 설정 해 준다 . 
 hostPathVolume ( hostPath : '/ var / run / docker . sock ', mountPath : '/ var / run / docker . sock '), 
 persistentVolumeClaim ( mountPath : '/ home / jenkins / . gradle ', claimName : ' msa 1 ', readOnly : false ) / / Gradle 캐시 를 저장 할 EBS 를 마운트 한다 . 
 이미 생성 된 EBS 의 ID 를 사용 할 경우 배포 될 팟 과 EBS 의 availability zone 은 동일 해야 한다 . 그렇 지 않 으면 팟 이 배포 되 지 않 는다 . 빌드 에 사용 되 는 EBS 는 모두 ip - 10 - yy - yy - yy . ap - northeast - 2 . compute . internal 노드 와 동일 한 availability zone 에 생성 해 두 었 다 . 
 EBS 를 마운트 하 여 사용 하 니 자바 서비스 의 빌드 속도 가 눈 에 띄 게 개선 되 었 다 ( MSA # 1 서비스 의 경우 는 8 분 → 2 분 , ApiGateway 5 분 → 2 분 으로 많이 단축 되 었 지만 Front Web 은 8 분 → 5 분 정도 로 살짝 단축 됨 ) . 그리고 클러스터 상 에 자원 이 많 을 경우 파이프라인 의 parallel 문 을 사용 하 여 java 로 된 MSA # 1 , ApiGateway 서비스 와 Front Web 서비스 를 빌 드 하 는 각각 의 젠킨스 슬레이브 팟 을 클러스터 에 동시 에 배포 하 여 빌드 를 병렬 로 진행 하 는 파이프라인 을 추가 하 여 배포 에 걸리 는 시간 을 더 단축 할 수 있 었 다 . 
 이제 마지막 으로 수동 으로 해 줘야 하 는 작업 이 남 아 있 는데 , 그것 은 바로 테스 팅 환경 이 하나 배포 될 때 마다 생성 된 ELB 에 Route 53 의 도메인 을 연결 하 는 작업 이 다 . 사실 도메인 연결 은 귀찮 긴 하 지만 별 것 아니 지만 새로 생성 된 ELB 의 시큐리티 그룹 을 설정 해 주 는 것 이 더 문제 다 . 테스 팅 환경 이 배포 될 때 마다 새로 생성 된 ELB 의 시큐리티 그룹 을 다시 설정 해 주 지 않 으면 테스 팅 환경 이 외부 에 노출 될 수 도 있 을 것 이 다 . 이제 이 문제 만 해결 한다면 배포 는 완전 자동화 를 달성 할 수 있 을 것 이 다 . 이 문제 는 Ingress 리소스 를 사용 하 여 ELB 를 한 개 만 고정 으로 사용 하 고 Ingress 에서 받 은 요청 을 호스트 값 에 따라 알맞 는 테 스팅 환경 으로 각각 포워딩 하 면 해결 할 수 있 을 것 이 다 . Ingress 는 apache 의 virtual host 와 같 은 역할 을 한다고 생각 하 면 될 것 이 다 . 그런데 Ingress 리소스 는 쿠 버네 티스 에서 기본 으로 지원 되 는 것 은 아니 라서 사용 하 려면 우선 ingress addon 을 설치 해 줘야 한다 . Ingress 를 addon 을 설치 하 면 kube - ingress 네임 스페이스 에 ingress - nginx 라는 로드 밸 런 서 를 만드 는데 , 이 로드 밸 런 서 역시 AWS ACM 인증서 를 사용 하 기 위해서 ACM 설정 을 위한 annotation 을 추가 해 준다 . ( ACM 인증서 사용 을 위한 annotation 은 이 글 의 윗부분 에서 찾 을 수 있 다 ) 이제 ELB 는 이 로드 밸 런 서 에서 만 사용 할 것 이 다 . 
 Ingress 가 적용 된 테 스팅 환경 의 구조 
 이제 ingress 리소스 를 설정 하 여 클러스터 에 추가 하 자 
 Ingress 설정 yaml 파일 
 name : alpha - cluster 
 - host : alpha - cluster 01 - www . testdomain . com 
 serviceName : alpha - cluster 01 - router 
 - host : alpha - cluster 01 - api . testdomain . com 
 - host : alpha - cluster 01 - m . testdomain . com 
 - host : alpha - cluster 02 - www . testdomain . com 
 serviceName : alpha - cluster 02 - router 
 - host : alpha - cluster 02 - api . testdomain . com 
 - host : alpha - cluster 02 - m . testdomain . com 
 # 나머지 도메인 도 똥 일 하 게 설정 해 준다 . 
 이제 ELB 는 Ingress 에 연결 된 1 개 만 남 았 고 테스 팅 환경 을 배포 하 면 Ingress 를 통해 접속 할 수 있 어 Route 53 과 Security Group 을 테스 팅 환경 을 배포 할 때 마다 수정 할 필요 가 없 어 지 게 되 었 으니 이제 배포 가 자동 화 되 었 다고 할 수 있 을 것 같 다 . 
 갱신 이 필요 한 서비스 만 새로 배포 하 기 
 이제 테스 팅 환경 을 배포 하 는 것 은 자동 화 하 였 는데 , 만약 수정 한 서비스 는 한 개 인데 테스 팅 환경 에 필요 한 모든 리소스 를 재 배포 해야 한다면 효율 적 이 지 않 을 것 이 다 . 원 하 는 하나 의 서비스 만 교체 하 는 파이프라인 을 만들 어 보 자 . 테스트 를 구성 하 는 서비스 는 Deployment 를 통해 팟 을 관리 하 니 kubectl set image 명령어 를 사용 하 면 지정 한 Deployment 에서 사용 하 는 팟 의 도커 이미지 가 수정 할 수 있 는데 , Deployment 리소스 는 사용 하 는 도커 이미지 가 변경 될 경우 팟 을 변경 된 이미지 를 사용 하 도록 재 배포 하 므로 이 를 이용 하 여 원 하 는 서비스 만 갱신 하 는 파이프라인 을 만들 수 있 다 . 
 MSA # 1 서비스 하나 만 갱신 하 는 파이프라인 예 , helm 이 아닌 . kubectl 컨테이너 를 사용 한다 . 
 def msa 1 ImageTag 
 envVar ( key : ' GRADLE _ OPTS ', value : '- Xmx 384 m '), 
 containerTemplate ( name : ' kubectl ', image : ' lachlanevenson / k 8 s - kubectl : latest ', ttyEnabled : true , command : ' cat ', resourceLimitMemory : ' 64 Mi ') 
 persistentVolumeClaim ( mountPath : '/ home / jenkins / . gradle ', claimName : ' msa 1 ', readOnly : false ) 
 이제 원 하 는 서비스 만 빌 드 하 여 배포 하 는 것 이 가능 해졌으니 좀 더 효율 적 으로 테스 팅 환경 을 사용 하 는 것 이 가능 할 것 이 다 . 
 쿠 버네 티스 를 사용 하 며 경험 을 쌓 아 보 자는 생각 으로 시작 한 작업 이 었 지만 실제로 테스 팅 환경 을 구축 하 면서 느낀 점 은 테스 팅 환경 을 구성 하 는 데 에 있 어서 도 편리 하 고 유용 했 다는 점 이 다 . 쿠 버네 티스 의 리소스 들 은 클러스터 가 AWS 든 GCE 든 상관 없이 클러스터 환경 이 구성 되 어 있 다면 얼마 든지 올려서 사용 할 수 있 으며 AWS 를 다루 는데 익숙 하 지 않 은 본인 의 입장 에서 도커 이미지 를 빌드 하고 쿠 버네 티스 리소스 를 생성 하 는 것 으로 손쉽 게 다른 서비스 들 을 테스 팅 환경 에 추가 할 수 있 다는 점 에서 상당히 매력 적 이 었 다 . 그리고 정말 잘 구성 한다면 테스 팅 환경 을 유지 하 는 데 드 는 예산 도 절약 할 수 있 는 가능 성 을 보여준다 . 사용 하 는 AWS 인 스턴스 수 가 줄어드 는 건 소소 한 덤 이 라고 볼 수 있 겠 다 . 
 정리 를 해 놓 고 보 니 쿠 버네 티스 자체 보다 는 배포 자동 화 에 대한 내용 이 더 많 아 진 것 같 다 . 필자 는 젠킨스 의 쿠 버네 티스 플러그인 을 사용 하 여 파이프라인 을 작성 하 였 지만 굳이 이 플러스 인 사용 없이 도 얼마 든지 배포 를 자동 화 시킬 수 는 있 을 것 이 다 . 하지만 젠킨스 슬레이브 팟 을 클러스터 에 배포 하 고 팟 에 포함 된 컨테이너 를 통해 빌드 를 하 는 것 은 나름 신선 한 경험 이 었 고 빌드 를 위한 컨테이너 를 별도 로 만들 어서 빌드 작업 에 사용 하 는 것 도 의외 의 편리 함 을 제공 하 여 한 번 사용 해 볼 만 한 가치 는 있 다는 느낌 이 들 었 다 . 만약 쿠 버네 티스 에 관심 이 있 고 사용 해 보 고 싶 지만 실제로 적용 하 는 데 부담 이 된다면 테스 팅 환경 과 그 배포 환경 을 쿠 버네 티스 를 이용 하 여 구성 해 보 는 것 부터 비교 적 가볍 게 시작 해 보 는 건 어떨까 ?