Intro


너무 추운 혹한기에서 살아남기

애플리케이션 개발도 개발이지만, 클라우드 환경에서 애플리케이션을 배포하고 운영하는 방법을 다루면서 전반적인 인프라 생태계에서 내가 개발한 애플리케이션의 정체성을 어떻게 나타내야 하는지에 대한 고민이 많아졌다.

단순히 애플리케이션만 딱 개발하고 끝난다면 쉬운 이야기일테지만, 현재 상황에선 애플리케이션만 개발하는 개발자는 경쟁력이 떨어질 수 있기 때문이다.

그래서 클라우드 네이티브한 애플리케이션 개발에 신경을 써야할지, 동작할 애플리케이션의 구조화에 집중할지 갈피를 잡기 어려웠다. 사실 이전부터 원했던 개발자로서의 이상향은 후자에 가까웠다.

애플리케이션의 기초 구조를 잘 구축해두고 잘 짜여진 설계를 바탕으로 바람직한 코드를 붙여나가는 것을 즐기는 개발자로 성장하고 싶었고, 그 가운데에서 더욱 몰입할 수 있다고 느꼈기 때문이다.

하지만 현재 IT 업계에서 원하는 인재상은 단순히 애플리케이션 개발에 집중하는 신입을 원하지도 않을 뿐더러 채용시장도 점점 좁혀지고 있다. 사실 이미 좁혀졌다고 봐도 과언이 아니다..

그래서 카카오 클라우드 스쿨에서는 애플리케이션 개발도 중요하지만, 거기서 끝나지않고 클라우드 환경 위에서 동작하는 애플리케이션을 어떻게 운영 및 유지보수 할것인지에 대해 기술적인 고민과 실력을 갖춘 인재를 요구한다고 생각이 들었고, 누구나 들으면 알 법한 여러 플랫폼 기업들에서도 비슷한 수준의 개발자를 원한다고 생각이 들었다.

그래서 나름대로 살아남는 개발자가 되자라고 결론을 내렸다. 살아남기 위해서는 내가 하고싶은 개발만 하는 개발자가 아니라, 많은 곳에서 나를 필요로 하는 개발자가 되어야 한다.




Day - 76

Jenkins와 Github Webhook을 통한 배포 자동화

젠킨스(Jenkins)는 Java로 제작된 오픈 소스로 코드 저장소(Repository)에 대한 CI(지속적인 통합)와 CD(지속적인 배포)를 무료로 사용할 수 있는 도구이다.

Jenkins를 이용하여 직접 개발한 애플리케이션을 빌드하고 배포하는 데에 필요한 프로세스를 실습해보던 중 Github에 코드를 push만 해도 애플리케이션을 빌드할 수 있는 방법이 있는지 궁금하여 찾아보니, Github에서 제공하는 Webhook을 사용해 의도한 바를 이룰 수 있었다.

먼저 깃허브 웩훅과 젠킨스를 통한 배포 자동화 절차를 어느정도 이해해야 한다. 배포 자동화 절차는 다음과 같다.

1. 저장소에 작업 내역 반영하기
로컬 저장소에서 개발을 진행하여 변경된 내역들을 원격 저장소에 push한다.

2. Github Webhook 빌드 트리거 실행
연동된 Github 원격 저장소에 push 이벤트가 발생하면 Github Webhook은 jenkins에게 애플리케이션을 빌드하라는 API 요청을 전송한다.

이 때, jenkins는 Github로부터 받은 API 요청을 통해 원격 저장소의 내용을 받아서 빌드를 실행한다. Spring Boot 프로젝트라고 한다면 빌드 후 jar 파일이 만들어지게 된다.

3. 배포 작업 진행
빌드가 완료되면 jenkins가 배포파일을 운영 환경으로 배포하고 실행시키면 된다.


Github Webhook이란?

Github의 웹훅(Webhook)이란 원격 저장소의 소스에 commit나 push 등의 이벤트가 발생하면 Jenkins와 같은 CI 서버에 해당 이벤트를 전달하는 기능이다.

웹훅은 역방향 API, Reverse API, Web Callback, HTTP PUSH API로 불리는데, 등록한 URL로 정보를 보내주는 수신봇의 역할을 수행하기 때문이다.

깃허브에서 웹훅을 통해 젠킨스에서 프로젝트를 자동으로 빌드하도록 해보자. 실습을 위해 EC2 인스턴스에 젠킨스를 설치해둔 상태임을 알린다.


Github Webhook(웹훅) 생성하기

원하는 저장소로 이동하여 Settins의 Webhooks에서 새로운 웹훅을 만들어보자.

웹훅을 생성하는 것은 매우 간단하다. 웹훅을 생성할 때, Payload URL은 http://[EC2 인스턴스 퍼블릭 아이피]:[젠킨스 포트]/github-webhook/ 형식으로 입력하면 된다. Content-type은 appliction/json 형식을 선택하였다.


웹훅 상태 확인하기

웹훅이 정상적으로 생성되었다면 Recent Deliveries에서 Health Check 상태를 점검하자. ✔ 표시가 되어있다면 정상적으로 트리거를 실행할 수 있다.

웹훅을 통한 젠킨스 프로젝트 빌드 여부 확인하기

연동한 웹훅에서 젠킨스에게 트리거를 받아서 전달하려면 push 이벤트가 있어야 하기 때문에 연동된 Github 저장소에서 변경 내역을 push하자.

이 때 젠킨스 프로젝트 설정에 빌드 트리거 여부가 활성화되어 있어야 한다.

깃허브 저장소에 push를 하니 젠킨스에서 자동으로 CI 빌드가 트리거됨을 알 수 있었다.

위와 같이 Github에 push 이벤트만 전달했을 뿐인데, 자동으로 새로운 빌드 내역이 진행되었다. 저장소에 push이후 젠킨스에서 빌드를 별도로 해야하는 번거로움을 줄일 수 있다는 점은 개발자에게 시간과 비용을 절감하는데 큰 도움을 줄 수 있을 것이라고 생각이 들었다.

또한, 배포를 자동화하는 절차를 최대한 간소화한다면 보다 퀄리티 높은 애플리케이션을 만드는데 집중할 수 있지 않을까?



Day - 77

자동화만 믿고 Webhook를 사용하면 안된다고?!

AWS EKS Cluster가 구축된 환경에서 Jenkins와 argoCD를 통해 CI/CD 파이프라인을 구축했는데 배포 과정을 모두 자동화할 수는 없었다.

내가 구축한 CI/CD 파이프라인의 대략적인 절차는 다음과 같았다.

  1. 개발 작업 내역 연동된 깃허브 저장소로 push한다.
  2. 저장소에 소스코드 push 여부를 확인한 후 직접 젠킨스 서버에서 빌드를 진행한다.
  3. 젠킨스에서 애플리케이션을 배포파일로 빌드한 후 이미지로 빌드하여 Docker Hub에 업로드한다.
  4. EKS Cluster에 디플로이먼트로 반영하기 위해 Docker Hub에 업로드된 이미지를 가지고 깃허브 저장소의 메니페스트를 수정한다.
  5. argoCD에서 메니페스트가 포함된 디렉토리의 변경을 감지하여 디플로이먼트에 배포를 진행한다.

그런데 2번 과정처럼 젠킨스에서 일일이 빌드해줘야 하는게 굉장히 번거롭게 느껴졌다. 이 때, Github Webhook로 배포 과정을 자동화할 수 있지 않을까? 생각이 들었다.

그래서 CI/CD 절차에 Github Webhook을 이용해보기로 하였다. GIthub Webhook을 통해서 저장소의 push 이벤트를 받아 젠킨스에 빌드 요청을 전달해주기만 하면 된다고 생각했다. 그러면 의도한대로 소스코드만 저장소에 push하면 웹훅을 통해 젠킨스에서 자동으로 빌드하고 배포될 줄 알았다.

Jenkins에서 빌드가 무한반복되는 이슈 발생!

그런데 웹훅을 이용하게 되면 심각한 문제가 발생할 수 있음을 깨닫게 되었다.

웹훅을 이용하면 해당 깃허브 저장소에 push 이벤트가 발생할 때마다 젠킨스에 빌드를 트리거하게 되는데 내가 구축한 CI/CD 파이프라인에서는 소스 코드 변경점을 push를 하면 CI 작업이 실행되는데, CD 작업이 끝나면 젠킨스가 해당 저장소에 SSH로 push를 하도록 구현되어있기 때문이다.

웹훅은 앞에서 말했듯이 push 이벤트가 발생하면 젠킨스로 빌드를 트리거하도록 설정했기 때문에 최초로 push 이벤트가 들어오고 젠킨스로 빌드 트리거 요청을 보내고, CD 작업 이후에도 젠킨스로 빌드 트리거 요청을 보내기에 끝없이 젠킨스로 빌드를 트리거하게되는 문제가 발생하게 된다.

애플리케이션 저장소와 메니페스트(k8s) 저장소를 분리하자.

웹훅을 통한 젠킨스의 무한 빌드 이슈를 해결하기 위한 솔루션은 생각보다 간단했다.

바로 애플리케이션 저장소와 메니페스트 저장소를 분리하는 것이다. 기존에는 애플리케이션과 EKS Cluster를 구성한 k8s 메니페스트 파일이 애플리케이션 저장소에 포함되어 있었기에 같은 저장소에 push 이벤트가 2번 발생하게 되었다.

직접 개발한 내역을 애플리케이션 저장소에 push하면 Webhook을 통해 Jenkins에 애플리케이션을 빌드 트리거를 발동 시킬 것이다.

Jenkins에서 CI를 진행하면서 애플리케이션 이미지를 빌드하고 Docker Hub에 업로드한 후 해당 이미지를 가지고 k8s 메니페스트 파일이 존재하는 새로운 저장소로 push하게 된다면 애플리케이션 저장소에는 최초 1번의 push 이벤트만 발생했기에 무한 빌드 이슈를 해결할 수 있게 된다.



Day - 78

CI/CD에 대한 개념과 Jenkins 설치하기

CI/CD 파이프라인을 구축하는 실습을 진행하면서 정작 CI/CD에 대한 개념에 대해서는 제대로 숙지하지 않은 것 않아 CI/CD에 대한 개념적인 부분부터 살펴보고 AWS EC2 서버에 CI/CD를 위한 도구인 Jenkins를 설치하는 것까지 복습해보려 한다.

CI/CD란?

CI/CD란 지속적 통합(Continuous Integration, CI)과 지속적 배포(Continuous Deployment, CD)를 의미하며 애플리케이션을 개발하는 것부터 시작하여 배포하는 단계까지 자동화하는 방식, 즉 코드 변경 사항이자동으로 빌드, 테스트 및 프로덕션에 배포되는 소프트웨어 개발 방식이다.

CI(Continuous Integration)란?

CI는 지속적 통합이란 뜻으로 간단히 말하자면 빌드 및 테스트 자동화 과정이라고 볼 수 있다.

개발자가 작성한 애플케이션에 대한 코드가 빌드 및 테스트되어 Github 등과 같은 형상관리 도구를 통한 Repository에 통합되기 때문에 여러 명의 개발자가 동시에 협업을 진행하는 과정을 이야기한다. 또한, 여러 명의 개발자가 개발 작업을 할 경우 서로 충돌할수있는 문제를 해결할 수 있다.

CI는 개발한 코드들의 변경사항을 정기적으로 커밋하여 모든 사람에게 동일 작업 기반을 제공할 수 있기에 사전에 문제를 방지할 수 있도록 도와주며, 소프트웨어의 품질을 개선할 수 있다.

CD(Continuous Delivery/Continuous Deployment)란?

CD는 2가지 뜻이 있는데, 지속적 제공 이나 지속적 배포 라는 뜻이 있다. 이를 간단히 말하자면 배포를 자동화하는 과정이라고 볼 수 있다.

지속적 제공(Continuous Delivery)의 경우 CI가 정상적으로 완료되었다면, 배포 단계를 거치기 전 최후의 검증을 수동으로 진행한 이후, 승인이 되었을 경우 배포를 진행하는 과정이다.

지속적 배포(Continuous Deployment)는 앞서 보았던 지속적 제공 절차를 자동화 함으로써, 개발한 코드의 변경 사항을 자동으로 프로덕션에 배포할 수 있게 된다. 지속적 제공 방식보다는 살짝 다르게 퀄리티의 수준을 유지한 채로 빠르게 사용자들에게 서비스를 제공할 수 있다.

결국 지속적 제공 및 지속적 배포 방식을 통해 빠른 시간 내 사용자들에게 동일한 품질의 서비스를 배포할 수 있게 되어 릴리즈 기간도 단축할 수 있을 뿐더러 개발의 효율성을 증진시킬 수 있다.

Jenkins를 컨테이너로 실행하기

CI/CD 절차를 제공해주는 도구들에는 Jenkins, Travis, Github Action, GitLab, CircleCI 등이 있는데, 이번 글에서는 AWS EC2에 DockerHub에 업로드된 Jenkins Image를 가지고 컨테이너로 실행하는 과정을 복습하려 한다.

Jenkins를 활용할 AWS EC2 인스턴스의 경우 ubuntu 20.04 OS로, 인스턴스 유형은 t2.small로 설정한 후 생성하였다.

프리티어인 t2.micro로 설정하여 인스턴스를 사용한다면 Jenkins 서버가 수시로 죽을 수 있기 때문에 t2.small로 설정하는게 편하다.

컨테이너로 실행할 Jenkins의 이미지는 LTS 버전을 택했고, Jenkins는 기본으로 8080포트를 사용하는데 임의로 7777포트로 포워딩하여 사용하였다.

Jenkins의 LTS(Long Term Support) 버전이란 안정화된 버전을 뜻하며 정기적으로 변경될 수 있다. 현재 기준으로 Jenkins의 LTS 버전은 2.289.1 버전이라고 한다.

docker pull jenkins/jenkins:lts

docker pull 명령을 통해 Jenkins 이미지를 다운로드하자.


docker run -d -p [젠킨스 사용 포트]:8080 -p 50000:50000 -v /jenkins:/var/jenkins -v /home/ubuntu/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock --name jenkins --restart=always -u root jenkins/jenkins:lts

그리고 docker run 명령을 통해 jenkins 컨테이너를 생성하고 실행하자.

Jenkins을 최초로 실행하면 Unlock을 해야한다. Unlock이 필요한 이유는 보안 상의 이유이다.

Jenkins는 많은 플러그인과 기능을 제공하며, 이러한 기능들은 민감한 데이터와 상호작용하거나, 시스템 자원에 대한 접근 권한을 가질 수 있기 때문에 관리자 계정에 대한 암호화된 액세스 제어가 필요하다.

그래서 Jenkins를 최초 실행할 때, Jenkins는 관리자 계정을 만들게 되는데, 이 관리자 계정의 액세스 권한을 부여하기 위해 Unlock Jenkins 페이지에서 암호화된 비밀번호를 입력하여 Unlock을 해야 한다. Unlock에 필요한 비밀번호는 최초로 Jenkins를 실행할 때 생성되며, 이 암호는 실행될 때마다 새롭게 생성된다.

결국 Unlock Jenkins 페이지에서 입력한 비밀번호는 Jenkins 구성 파일에 저장되며, 이후에는 액세스 토큰이나 API 키 등을 통해 Jenkins에 로그인할 수 있다. 이러한 보안 기능은 사용자 데이터와 자원을 보호하며, Jenkins를 안전하게 운영하기 위해 중요하다고 한다.


아래 명령어를 통해 초기 비밀번호를 확인하고 입력하면 된다.

cat /var/lib/jenkins/secrets/initialAdminPassword

두 가지 선택 창이 나오는데, Jenkins에서 추천해주는 추천 플러그인을 설치하기 위해 Install suggested plugins를 선택하자.

Gradle이나 Git, Github, Pipeline, SSH 등 다양한 플러그인을 자동으로 설치해준다.

다음으로 Jenkins에서 사용할 관리자 계정을 입력한다.

마지막으로 Jenkins의 root url을 설정하면 된다. 자동으로 현재 서버의 IP주소 + 포트로 지정되니 그냥 넘어가도 된다.

여기까지 Jenkins 이미지를 컨테이너로 생성하고 실행하고 초기 설정을 해보았다. CI/CD 도구에서 가장 대중적인 Jenkins를 쓰는 이유가 무엇인지 아직은 잘 모르겠지만, 사용해보면서 몸소 체감해보고 싶다.



Day - 79

EKS Cluster에 argoCD 설치하기

ArgoCD는 GitOps 스타일의 지속적 배포(CD, Continuous Delivery, Continuous Deployment)를 지원하는 도구이다.

GitOps는 소프트웨어 개발과 운영의 프로세스를 조직하는 방법론 중 하나로, Git 기반의 협업적인 소프트웨어 개발과 배포를 추구하며, 소프트웨어 배포의 지속적인 통합과 배포를 단순화하는 데 중점을 둔다.

argoCD는 Kubernetes와 같이 선언적으로 동작하며, 연동한 Github Repository에 변경된 내용을 Kubernets Cluster에 동기화한다.

argooCD에 Github Repository의 특정 경로를 지정하고, 해당 경로에 manifest 파일들을 위치시키면 manifest에서 정의한 kubernates의 정보를 argoCD가 자동으로 배포해준다.

이러한 argoCD를 AWS EC2 인스턴스에 구축한 EKS Cluster 위에 설치해보자.

AWS EC2 인스턴스에 EKS Cluster 구축하기 위한 사전 준비하기

AWS EC2 인스턴스에서 argoCD를 설치하려면 EC2 인스턴스에 Kubernaters Cluster가 구축되어 있어야 한다. 여기서는 AWS를 사용하는 김에 AWS에서 지원하는 EKS를 이용하여 Kubernates Cluster를 구축하였다.

EKS Cluster를 구성하기 위해서는 AWS CLI, eksctl, kubectl을 설치해야 한다. 여기서는 argoCD를 EKS Cluster에 설치하는 것이 주 목적이니 AWS CLI, eksctl, kubectl을 설치하는 과정에 대해서는 가볍게 명령어만 살펴보고 넘어가려 한다.

AWS CLI Install

# cli 환경 설치를 위한 압축파일 다운로드
$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"

# 압축 파일 풀기위한 패키지 설치 및 해당 파일 압축 해제
$ apt-get -y install unzip
$ unzip awscliv2.zip

# 설치 진행
$ sudo ./aws/install

# aws 커맨드 설치 여부 확인
$ aws

AWS에 CLI로 로그인하기
AWS에 CLI로 로그인하기 위해서는 AWS IAM 사용자 정보가 있어야 한다. AWS IAM에서 추가한 사용자의 엑세스 정보를 입력한다.

$ aws configure
AWS Access Key ID [None]: [IAM 사용자의 accesskey 입력]
AWS Secret Access Key [None]: [IAM 사용자의 secret accesskey 입력]
Default region name [None]: ap-northeast-2
Default output format [None]:

eksctl Install

# eksctl 설치파일 다운로드
$ curl --silent --location \
"https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
 
# 명령어를 내 서버로 이동
$ sudo mv /tmp/eksctl /usr/local/bin
 
# eksctl 버전 확인
$ eksctl version

kubectl Install

# 명령어 다운로드
$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"

# 설치 
$ sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

# 버전 확인
$ kubectl version --client

자 이제 여기까지 하면 EKS Cluster를 구축하기 위한 준비가 완료되었다.

EKS Cluster 구축하기

EKS Cluster를 구축하기 위한 명령어를 입력하자. 실제로 AWS EKS Cluster를 생성하는 페이지에서 직접 생성해도 무방하지만, 여러 설정을 페이지 단위로 이동하면서 하기엔 번거로울 수 있기에 명령어 하나로 쉽게 구축할 수 있다.

$ eksctl create cluster --vpc-public-subnets <퍼블릭서브넷ID 1>,<퍼블릭서브넷ID 2> --name <eks 클러스터 이름> --region ap-northeast-2 --version 1.24 --nodegroup-name <eks 노드그룹 이름> --node-type t2.small --nodes 2 --nodes-min 2 --nodes-max 5- 

EKS Cluster에서 앞서 생성한 클러스터를 확인할 수 있다.

마찬가지로 AWS의 인프라 프로비저닝 서비스인 CloudFormation에서도 생성한 클러스터의 스택을 확인할 수 있다.

EKS Cluster 위에 argoCD 설치하기

argoCD 공식문서의 가이드를 따라서 yaml 파일을 통해 argoCD를 설치해보자.

먼저 argocd라는 namespace를 생성하고 Pod를 배포하는 명령어를 입력한다.

# argoCD를 설치하기 위한 네임스페이스 생성
$ kubectl create namespace argocd

# argoCD 설치하기
$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# argoCD의 타입을 로드밸런서로 구성하기
$ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'

argoCD를 클러스터 내에 설치한 이후엔 웹 콘솔을 통해서 개발자가 원하는 설정들을 진행해야 하는데, 외부에서 접속할 수 있도록 argoCD Service의 타입을 로드밸런서로 변경하였다.

이후 argoCD Service에 로드밸런서 타입으로 DNS 주소가 생성된 것을 확인하면 된다.

해당 DNS 주소로 웹 콘솔에 접속해보자.

Argo CD는 argocd-initial-admin-secret이라는 Secret에 초기 비밀번호가 저장되어 있다.

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo

Username은 admin을 입력, Password는 위 명령어를 통해 찾아낸 초기 비밀번호를 입력하면 된다.

로그인에 성공하여 argoCD 웹 콘솔을 사용할 수 있게 되었다.


AWS EC2 인스턴스에 EKS Cluster를 구축하고 그 위에 ArgoCD를 설치하여 Kubernates Cluster에 배포할 수 있는 환경을 만들어 보면서 과정 자체도 복잡하지만, argoCD를 도입하는 이유를 확실히 숙지하고 넘어가는 것이 더 중요하다고 느껴졌다.



Day - 80

Jenkins에서 빌드할 때 Java 호환성 문제 해결하기

파이널 프로젝트를 시작하면서 먼저 애플리케이션 이미지를 Jenkins로 CI 작업을 구현하는 것을 설계하고 테스트해보는 중에 Spring Boot 프로젝트의 Build Job이 실패하는 문제가 발생하였다.

빌드가 실패한 로그는 다음과 같다.

빌드 실패 로그

+ gradle clean build
Starting a Gradle Daemon (subsequent builds will be faster)

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'dreamteamapplication'.
> Could not resolve all files for configuration ':classpath'.
   > Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.0.2.
     Required by:
         project : > org.springframework.boot:org.springframework.boot.gradle.plugin:3.0.2
      > No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.0.2 was found. The consumer was configured to find a runtime of a library compatible with Java 11, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.6' but:
          - Variant 'apiElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.0.2 declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares an API of a component compatible with Java 17 and the consumer needed a runtime of a component compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '7.6')
          - Variant 'javadocElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.0.2 declares a runtime of a component, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn't say anything about its target Java version (required compatibility with Java 11)
                  - Doesn't say anything about its elements (required them packaged as a jar)
                  - Doesn't say anything about org.gradle.plugin.api-version (required '7.6')
          - Variant 'mavenOptionalApiElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.0.2 declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares an API of a component compatible with Java 17 and the consumer needed a runtime of a component compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '7.6')
          - Variant 'mavenOptionalRuntimeElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.0.2 declares a runtime of a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component compatible with Java 17 and the consumer needed a component compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '7.6')
          - Variant 'runtimeElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.0.2 declares a runtime of a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component compatible with Java 17 and the consumer needed a component compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '7.6')
          - Variant 'sourcesElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.0.2 declares a runtime of a component, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn't say anything about its target Java version (required compatibility with Java 11)
                  - Doesn't say anything about its elements (required them packaged as a jar)
                  - Doesn't say anything about org.gradle.plugin.api-version (required '7.6')

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 13s

에러 구문이 굉장히 길지만 아래 구문이 가장 중요하게 느껴졌다.

- Incompatible because this component declares an API of a component compatible with Java 17 and the consumer needed a runtime of a component compatible with Java 11

해당 로그를 통해서 Java 17과 Java 11의 호환성 문제 때문임이라는 것을 알 수 있었다.

Jenkins에서 Java 17을 사용할 수 없는가?

필자가 Jenkins에서 빌드한 Spring Boot 프로젝트의 경우 Java 17을 통해 개발된 애플리케이션이다.

그런데 위와 같은 빌드 에러가 발생한다는 것은 결국 Jenkins에서 Java 17을 지원하지 않는 것인가?라는 생각이 들게 하였다.

찾아보니

현재 기준으로 Jenkins의 LTS버전은 2021년 10월 출시된 2.289.2 버전인데, 이 버전에서는 Java 17을 공식적으로 지원하지 않는다고 한다.

Docker Hub의 이미지 저장소에서 관련 정보를 찾아보니 위 사진처럼 Jenkins LTS 버전에서는 Java 11을 권장하고 있다.

나는 Java 17을 사용하는 Jenkins 이미지를 사용하지 않았을 뿐더러 LTS 버전의 이미지에서 별도의 설정조차 하지 않았기에 자동으로 내가 사용한 Jenkins에서는 Java 11로 설정되어 있었을테고, 내가 빌드하고자 했던 애플리케이션에서 사용하는 Java 17과 호환성 충돌이 발생한 것이라고 판단하게 되었다.

Jenkins에서 사용할 Java의 호환성 맞추기

그렇다면 이 문제를 위해서는 애플리케이션의 Java 버전을 11로 내리던가, Jenkins에서 Java 버전을 17로 올리던가 둘 중에 하나를 선택해야 했다.

정 LTS 버전의 Jenkins에서 Java 17을 사용하려면, Java 환경 변수를 설정하여 사용할 수는 있지만, 공식적으로 제공하는 방법이 아닌지라 권장하는 Java 버전을 제공하는 이미지를 사용하는 것이 좋다고 한다.

결국 애플리케이션의 Java를 변경하는 것보단 Jenkins의 Java 버전을 변경하는 것이 더 나을 것이라 생각하여 사용할 Jenkins 컨테이너를 Java 17을 지원하는 Jenkins의 이미지로 사용하기로 결정하였다.

찾아보니 jdk17 버전의 이미지를 지원하는 Jenkins 이미지를 사용할 수 있어서 해당 이미지로 Jenkins 컨테이너를 다시 생성하였다.

Jenkins 초기설정을 마치고 다시 Spring Boot 프로젝트 애플리케이션 을 빌드해보니 정상적으로 Gradle Build가 되는 것을 확인할 수 있었다.


이번 Jenkins에서의 Java 호환성 충돌로 인한 빌드 에러를 통해서 직접 개발자가 제어할 수 없는 곳까지 잘 고려해야 한다는 것을 느꼈고, 어떤 기술이든 공식문서 등 공식적으로 제공하는 기술의 범위를 꼼꼼히 살펴보는 것을 우선적으로 진행해야 함을 몸소 체감하였다.






Final..

카카오 클라우드 스쿨에서 17주간의 교육과정이 마무리되었다.

클라이언트부터 시작해서 서버단까지의 다양한 CS와 언어, 프레임워크 등을 통해 기초적인 개발지식을 단단히 할 수 있었고, 클라이언트와 서버를 통해 만든 애플리케이션 앱을 운영할 클라우드 네이티브 및 인프라 환경을 구축하는 것까지 배우고 적용해보는 시간을 가졌다.

이제 최종 프로젝트에서 배운 걸 잘 활용해야 한다.

먼저 배웠던 내용들을 온전히 내것으로 만들기 위해서 동작 원리나 개념에 대해서 숙지해야 한다. 그리고 배웠던 것들을 프로젝트로 녹여낼 때 직접 설계하고 개발에 참여하면서 그저 되는대로 구현하는 것이 아니라 왜 이렇게 구현할 수밖에 없었는지, 더 성능을 향상시킬 수는 없었는지에 대한 고민이 들어가야 한다고 생각한다.

카카오 클라우드 스쿨의 모든 커리큘럼이 끝난 것은 아니지만 교육과정 자체는 종료되었기에 조급하고 초조한 마음이 더 커졌다. 그래도 여태 배운 것들이 내 성장에 도움이 되었음을 증명하기 위해서 최선을 다해서 프로젝트와 코딩테스트를 준비하려고 한다.



혹여 잘못된 내용이 있다면 지적해주시면 정정하도록 하겠습니다.

게시물과 관련된 소스코드는 Github Repository에서 확인할 수 있습니다.

참고자료 출처

profile
찍어 먹기보단 부어 먹기를 좋아하는 개발자

0개의 댓글