Jenkins로 CI/CD를 구축하면 빌드를 위한 agent 서버를 Master와 연결하고 특정 서버에서 빌드 배포를 수행하도록 구성하는 경우가 많습니다. 그러다 보면 관리 대상 서버가 늘어나게 됩니다.
Jenkins kubernetes plugin을 사용하면 플러그인이 쿠버네티스 클러스터에 Pod로 agent를 생성하여 Pod의 컨테이너에서 Job을 수행하도록 할 수 있습니다. 그리고 Job이 끝나면 사라집니다. 즉 유지할 필요가 없습니다.
클러스터에서 빌드 배포를 수행하도록 설정하면 다음과 같은 장점들을 얻을 수 있습니다.
그럼 직접 설정해 보겠습니다.
테스트를 위해 클러스터 내에 Pod로 Jenkins 서버를 신규 생성했습니다. Jenkins의 Plugin Manager에서 Kubernetes를 검색하면 Kubernetes라는 플러그인이 있는데 해당 플러그인을 설치합니다.
설치가 시작되면 의존 플러그인들이 같이 설치 됩니다.
이제 jenkins가 pod를 생성할 쿠버네티스 클러스터 정보를 설정해야 하는데 설정 화면은 Jenkins 관리 - 시스템 설정 - Cloud란에 있습니다. 최신 버전의 jenkins에서는 별도의 페이지로 분리됐으니 텍스트를 클릭하여 설정 페이지로 이동합니다. Add New Cloud를 눌러 Kubernetes를 클릭하고 Kubernetes Cloud details... 를 눌러 클러스터 정보들을 설정합니다.
위와 같은 설정란들이 보일 텐데 사용자의 환경에 따라 필요한 설정들이 다를 것입니다. 여기서는 연결할 클러스터의 kubeconfig 파일을 jenkins credential에 sercret file로 업로드하여 Kubernetes API와 통신하도록 설정하겠습니다. (Jenkins Master가 클러스터 내에서 Pod로 작동한다면 ServiceAccount를 할당해 Kubernetes API와 통신하도록 할 수도 있다.)
정상 통신이 확인되었으면 샘플 Pipeline 스크립트를 실행하는 테스트 job을 만들어 정상 여부와 작동 방식을 확인해 볼 수 있습니다.
빌드를 시작하면 신규로 만든 job의 이름이 붙은 새로운 Agent가 빌드 목록에 등장하게 되고 Pod를 조회해 보면 지정한 네임스페이스에 신규 Pod가 생성된 것을 확인할 수 있습니다.
kubectl로 Pod를 보면 샘플 스크립트에서 정의한 maven 컨테이너 외 1개가 더 작동인 것을 볼 수 있습니다. Jenkins 콘솔 출력을 보면 파이프라인을 수행하기 전에 빌드를 수행할 Pod의 내용을 확인할 수 있는데 샘플 스크립트에서 정의한 PodTemplate에 jnlp 컨테이너가 자동으로 추가된 것을 확인할 수 있습니다. jnlp 컨테이너에 jenkins agent가 실행되고 jenkins master와 통신하는 역할을 하는데 플러그인이 자동으로 Pod에 추가한 것입니다.
샘플 Pipeline 스크립트를 통해 정상적으로 작동되는 것을 확인하였고 전반적인 실행 과정을 보았으니 이제 PodTemplate을 직접 만들어 보겠습니다. Jenkins agent로 사용할 Pod Template을 만드는 방법은 Global Pod Template과 Piepeline 스크립트에 직접 정의하는 방법 두 가지인데 이미 사용 중인 Job에 Pod Template을 적용하려면 Global Pod Template을 정의하여 쉽게 설정이 가능합니다.
Jenkins 시스템 설정에서 Kubernetes를 설정했던 페이지에 보면 Pod Template details...라는 버튼이 있는데 이곳에 Pod를 쉽게 정의할 수 있습니다.
Pod에 필요한 정보들을 입력하고 사용할 이미지를 지정해 줍니다. 여러 컨테이너를 설정할 수도 있는데 주의할 점은 jnlp 컨테이너가 추가되니 가급적 jnlp 컨테이너를 추가해 오버라이딩 되지 않도록 해야 합니다.
그 외 Pod 설정에 입력할 수 있는 다양한 설정들도 아래에 있습니다.
특정 Node에서 작동하게 하려면 nodeSelector, env, hostPath volume 등 Kubernetes Pod에 필요한 기존 정보들을 대부분 설정할 수 있습니다. 특히 Labels 같은 경우 pipeline 스크립트에서 쓸 노드를 명시할 때 쓰이므로 유니크한 이름으로 지정해야 합니다. 위 예시에서는 하나의 Pod에 test, test2라는 컨테이너가 ubuntu 이미지를 사용하게 설정하였습니다.
이렇게 Global Pod template을 한 번 정의해 두면 나중에 스크립트에서 이 template을 상속하여 merge 하거나 overwrite 할 수도 있습니다. 그럼 앞에서 정의한 Pod를 이용해 간단한 스크립트를 실행하는 Declarative pipeline 만들어 테스트해 봅시다.
Build stage에서 Agent를 생성해 Stage의 간단한 쉘 스크립트를 지정한 컨테이너에서 병렬로 수행하게 하는 간단한 예제입니다. Labels로 수행할 Template을 명시하고 stage의 steps에 수행할 컨테이너를 명시했습니다. 그리고 각 컨테이너에서 echo 수행 후 종료됩니다.
이처럼 간단하게 Global Pod Template을 기존에 있던 pipeline script에 쉽게 연결할 수 있습니다. 필요하다면 하나의 Pod에서 여러 컨테이너가 아닌 Agent를 복수개 생성해서 병렬로 수행하도록 구성할 수도 있습니다.
Pipeline 스크립트에 직접 Pod Template을 정의하려면 Pod yaml 내용을 직접 적거나 플러그인에서 제공하는설정에 맞춰 작성하면 됩니다. 일단 플러그인에서 제공하는 설정들은 링크에서 확인할 수 있습니다. 이 설정들에 맞춰 podTemplate을 pipeline 스크립트에 직접 작성해 보겠습니다.
Global Pod Template으로 정의했던 것을 pipeline script로 작성한 내용입니다. 플러그인의 podTemplate 설정을 통해 Pod를 정의하고 이후 파이프라인 스크립트 내용들이 추가되는 구조입니다.
먼저 Pod yaml 내용을 변수에 문자열로 정의합니다. 그리고 플러그인에서 제공하는 설정인 podTemplate에 yaml 변수를 지정합니다. yaml을 정의할 필요 없이 podTemplate 설정만으로도 할 수 있는데 만약 플러그인 설정 중에 필요한 게 없다면 yaml에 정의하면 됩니다.
만약 podTemplate 설정에 필요한 게 없다면 yaml에 정의한 후 yaml과 설정을 override 하는 방법을 사용하면 됩니다.
예를 들어보면 위 예제에 Pod yaml에 tolerations 내용이 있는데 podTemplate 설정에는 tolerations 설정을 지원하지 않습니다. 그래서 yaml에 tolerations를 추가했고 스크립트 설정에는 nodeSelector를 추가했습니다.
그럼 yaml에 정의한 내용과 podTemplate에 설정된 내용이 override(기본값) 됩니다. 그래서 yaml로 특정 taint를 가진 node들에 접근 가능한 tolerations을 부여했으니 플러그인 설정으로 정의한 nodeSelector로 인해 해당 노드에 할당이 가능 해지고 이 노드에서만 Pod가 생성됩니다. 이처럼 현재 사용 중인 환경이나 사용자의 필요에 맞춰 설정하면 됩니다.
실행하면 앞에서 설정한 Global 방식과 동일하게 작동합니다.
Kubernetes plugin for jenkins를 사용하면 앞에서 언급했던 장점들을 쉽게 얻을 수 있습니다. 그리고 애플리케이션 별로 agent를 생성해 병렬로 Job을 수행하여 빌드 시간을 줄이는 시도를 해볼 수 있습니다.
동적 agent와 kaniko 또는 dind(Docker in Docker)를 사용하면 각 agent에서 도커 이미지 빌드를 병렬로 수행하도록 할 수 있습니다.
Docker의 경우 docker-compose를 사용하지 않으면 하나의 Docker 엔진에서 동시에 이미지 빌드를 할 수 없어 순차적으로 진행하게 되는데 docker-compose를 사용하지 않고도 각 agent에서 dind나 kaniko 컨테이너 이미지를 사용해 병렬 수행하도록 구성할 수 있습니다.
무엇보다 Docker 이미지 빌드만을 위한 별도의 서버나 클라우드 서비스를 추가할 필요도 없습니다.
다음에는 도커에서 도커를 빌드할 수 있는 dind(Docker in Docker)나 kaniko에 대한 내용을 자세히 소개해 보겠습니다.