seoyyyy-dev
젠킨스 CI/CD - 메이븐과 넥서스 통합 및 자바 API 릴리즈용 프리스타일 작업 생성 본문
젠킨스 CI/CD - 메이븐과 넥서스 통합 및 자바 API 릴리즈용 프리스타일 작업 생성
seoyyyy 2025. 7. 8. 00:19이제 젠킨스가 소스 코드 리포지터리에서 자바 API의 원시코드를 가져오는 방법과 빌드 도구를 사용해 전체 빌드 수명 주기 단계를 거쳐 아티팩트를 최종 목적지인 넥서스 리포지터리로 배포할 때까지 방법을 실습해본다.
우선 소스 코드 리포지터리를 깃을 이용할 것이므로 깃이 컴퓨터에 설치되어있어야한다.
아래 경로로 이동하여 자신의 컴퓨터 환경에 맞는 깃 버전을 다운로드하여 설치해준다.
Git - Downloads
Downloads macOS Windows Linux/Unix Older releases are available and the Git source repository is on GitHub. Latest source Release 2.50.0 Release Notes (2025-06-16) Download Source Code GUI Clients Git comes with built-in GUI tools (git-gui, gitk), but ther
git-scm.com
😎 깃(Git) 프로세스 이해
깃에 대해서는 거의 다 알거라 생각하고 책의 내용에서 간단하게만 정리하도록 한다.
1.단계: 로컬 리포지터리 생성
프로젝트 디렉터리에 로컬 리포지터리를 생성한다. 명령 프롬프트를 열고 cd 명령으로 프로젝트 디렉터리로 이동한다.
git init 명령어를 실행해 빈 로컬 코드 리포지터리를 생성한다.
그럼 위와같이 .git 폴더가 생성된 것을 확인할 수 있다. .git 폴더는 숨겨진 폴더로 dir 명령어에 /a 옵션을 붙이면 확인가능하다.
2.단계: 깃랩에 중앙 리포지터리 생성
깃허브가 아닌 깃랩에서 프로젝트를 한번도 직접 생성해본적이 없지만 책과 똑같이 진행해보기 위해 깃랩 계정을 생성해주었다.
가입절차를 마치면 create or import your first project 라는 페이지가 나타나는데 우리 프로젝트 이름이랑 똑같이 해주겠다.
그러면 아래와 같은 화면이 나오는데 이거저거 설정하라는 창이 뜨지만 실습 외에는 사용하지 않을 것이므로 대충 무시해주도록 한다.
참고로 아래 Settings를 선택하여 나타난 General 로 이동해보면 Project visibility(가시성 수준) 를 선택하는 부분이 있는데 Private(비공개)로 둔다.
3.단계: 로컬 리포지터리로 커밋
이제 브랜치를 생성하고 코드를 로컬 리포지터리로 커밋해보도록 하겠다.
아까 열어둔 명령 프롬프트에서 프로젝트 디렉터리에 위치해있지 않다면 cd 명령을 사용해 프로젝트 디렉터리로 이동한다.
좀전에 git init을 통해 빈 리포지터리를 생성했는데 이때 git branch 명령을 사용하면 브랜치가 존재하지 않는 것을 확인할 수 있다.
그럼 checkout -b <브랜치이름> 명령을 실행해 첫 번째 브랜치를 생성해주도록 한다.
FirstBranch라는 이름의 브랜치를 생성해주었다.
이제 프로젝트 경로에 .gitignore 파일을 생성해 깃에 올리지 않아야 할 파일들을 설정해주자.
나는 intellij 에서 기본 제공을 해주는건지 모르겠는데 이미 기본적인 항목들이 작성된 .gitignore가 존재했다.
만약 .gitignore가 존재하지 않다면 최소 아래 네개는 설정해주자.
.settings
Target
.classpath
.project
이제 코드를 브랜치에 커밋하기 전 git add 명령어를 통해 스테이징 영역에 API 코드를 추가한다.
지금 초록색으로 보이는 파일들이 스테이징 영역에 추가된 것이다.
이제 다음 명령을 이용해 커밋할 때 필요한 사용자 이름과 이메일을 지정해준다.
설정을 올바르게 했는지 확인하려면 git config list 명령어를 실행하도록 한다.
이제 commit 명령을 이용하여 변경사항을 FirstBranch로 커밋해보자.
gitignore에 설정된 파일은 제외된 후에 남은 파일들만 커밋된 것을 확인할 수 있다.
4단계: 로컬 리포지터리의 코드를 깃랩으로 푸시
이제 깃랩 중앙 리포지터리로 푸시해보기로 한다.
로컬 리포지터리의 브랜치에서 중앙 리포지터리로 푸시하기 위해서는 다음 명령어를 실행한다.
명령 실행 시 깃 자격 증명 인증창이 나타나는데 여기에 깃 사용자 이름과 비밀번호를 입력하고 OK버튼을 클릭한다.
git push -u <깃 랩 리포지터리 URL> <브랜치 이름>
푸시에 성공하면 깃랩 사이트에서 아래와 같이 확인할 수 있다.
깃랩에서는 따로 브랜치를 생성해주지 않았는데도 아래 명령어를 실행함으로써 FirstBranch가 생성이 되었다.
git push -u https://gitlab.com/jenkinsbookexample/JenkinsBookExample.git FirstBranch
5단계: 깃랩의 리포지터리에 마스터 브랜치 생성
각 개발자의 변경사항을 모두 검토한 후 병합에 이용될 메인 브랜치를 만들어야 한다.
나는 main 브랜치가 기본으로 생성되었기 때문에 따로 Master 브랜치를 생성하지 않을 것이지만 만약 브랜치를 생성하고자 한다면 아래 New Branch 버튼을 선택해서 생성해주면 된다.
만일 브랜치를 삭제해주고 싶다면 Code > Branches 메뉴로 이동해 삭제할 브랜치의 우측 더보기 아이콘을 클릭한 후 Delete branch 를 선택해주면 된다.
이제 넥서스 리포지터리에 대해 살펴보자.
😀아티팩트란?
우선 넥서스 리포지터리를 알아가기 앞서 아티팩트란 빌드 과정에서 생성되는 산출물을 말하며 자바 API 프로젝트의 경우에는 빌드과정에서 생성되는 .jar 파일이 아티팩트이다.
😁 넥서스 리포지터리
여러 버전의 소스 코드를 관리할 때 Git을 사용했다 하면 각기 다른 버전의 .jar 파일에서 생성한 파일(아티팩트)를 관리하는 시스템(리포지터리)도 추가로 필요하다. 이 때 넥서스가 사용된다.
넥서스는 아티팩트를 릴리즈하고 필요에 따라 사용자가 아티팩트를 다운로드 할 수 있는 기능을 제공하는 플랫폼이다.
그중 mvnrepository.com 에서 제공하는 리포지터리로 배포하면 웹에서도 접속할 수 있어 편리하며, 여기에 배포된 아티팩트는 누구나 다운로드해서 사용할 수 있다. 그러나 누구나 접근하는 것이 아니라 같은 조직의 사람들만 접속할 수 있도록 하려면 조직 내부 네트워크에 넥서스 리포지터리를 설치해야 한다.
🥸 넥서스 리포지터리 설치
https://www.sonatype.com/products/sonatype-nexus-oss-download
Free Sonatype Nexus Repository Download
Get your free Sonatype Nexus Repository download today and accelerate your DevOps pipelines with the next iteration of Nexus OSS.
www.sonatype.com
위 URL 경로에서 넥서스 리포지터리의 압축파일을 다운로드한 후 압축을 해제한다.
폴더안엔 다음과 같은 구성으로 되어있다.
- nexus-3.81.1-01: 넥서스 시스템 관련 파일들이 있다. nexus.exe는 하위폴더의 bin/ 폴더에 위치해있으며 넥서스 실행파일이다.
- sonatype-work: 이 폴더에는 버전별로 생성한 아티팩트 리포지터리의 데이터가 저장된다.
😘넥서스 리포지터리 시작
넥서스 서버의 IP와 포트를 설정해야 하는데 이는 넥서스 설치 경로의 etc 폴더 내 nexus-default.properties 파일에서 설정할 수 있다.
넥서스를 시작하기 앞서 이 nexus-default.properties 파일을 설정해보자
넥서스를 실행할 때 사용할 포트는 application-port 를 설정해주도록 하고, application-host에는 시스템의 IP 주소를 설정하면 된다.
실습에서는 나의 컴퓨터의 IP주소와 포트는 8081(기본값)로 입력해주도록 하겠다.
IP주소와 포트 설정이 끝나면 이제 명령 프롬프트창에서 넥서스 설치경로의 bin 폴더로 이동해 Nexus.exe /install <서비스 이름> 으로 서비스를 설치한 후 nexus.exe /run 명령을 실행하도록 한다.
라고 책에는 되어있지만....
나는 같은 명령어를 입력해보니 에러가 났다..
그래서 요리조리 검색하다 보니 현재 버전에서는 윈도우일때 다른 명령어를 입력해야 하는 것 같았다. (아래 주소 참고)
https://help.sonatype.com/en/install-nexus-repository.html
Install Nexus Repository
The Nexus RepositoryTM distribution archives combine the application and required resources in an archive file. When testing Nexus RepositoryTM on a local workstation, the files may be extracted and run in any environment that supports a Java runtime.Many
help.sonatype.com
위에 작성해놓은 주소로 이동하면 bin/install-nexus-service.bat 을 먼저 실행해준 뒤 아래 명령어를 입력한다.
.\nexus.exe //ES//SonatypeNexusRepository
만일 에러가 난다면 로그를 좀더 자세히 보기 위해 명령어를 //TS// 로 변경한 뒤 실행하면 에러내역에 대해 더 자세하게 볼 수 있다.
.\nexus.exe //TS//SonatypeNexusRepository
난 참고로 nexus 3.81.1 버전은 Java17 이상을 지원하기 때문에 환경변수까지 Java17로 맞춰주었고 명령 프롬프트에서 Java -Version을 확인까지 해주었는데도 이상하게 예전에 다운로드 해두었던 C:\Program Files\Java\jre1.8.0 을 읽어와서 버전이 맞지 않다고 인식하여 실패했었다...
계속 삽질하다 결국 .\nexus.exe //TS//SonatypeNexusRepository 명령어를 사용하여 원인을 밝혀냈다.......
결국 그냥 jre1.8.0 은 현재 사용하지 않아 삭제해주었더니 17버전을 잘 찾아왔다. (아직도 왜 그런지 원인을 모르겠다..)
실행에 성공하면 다음과 같이 서버가 실행되는 것을 확인할 수 있다.
//TS// 로 실행하는 경우 위처럼 실시간 로그를 확인할 수 있고 //ES//로 실행하면 백그라운드로 실행되어 서비스에서 확인 가능하다.
😊 넥서스 리포지터리 매니저 접속
이제 브라우저를 열어서 앞에서 설정한 http://<IP주소>:<포트>를 입력해 서버에 접속해보자
그럼 아래와 같은 넥서스 리포지터리의 메인화면을 확인할 수 있다.
우선 계정이 존재하지 않으므로 회원가입을 해야하는데 우측 상단의 Log in 버튼을 클릭한다.
그럼 관리자 admin 유저의 Username과 Password를 입력해주면 되는데 나는 비밀번호를 설정한 기억이 없다.
글을 살펴보니 팝업에 작성된 경로인 D:\nexus-3.81.1-01-win-x86_64\sonatype-work\nexus3\admin.password 파일을 열어 기입된 암호문을 입력해주면 된다.
Username에 admin을 입력하고 해당 파일의 텍스트를 복사해와 비밀번호를 입력해주니 넥서스 리포지터리 매니저가 환영인사를 해준다. Next 를 눌러보자
그러면 관리자의 새로운 비밀번호를 설정하는 화면이 나온다.
새로운 비밀번호를 입력한 후 Next를 누르자.
그다음은 계속해서 Next를 눌러주면 된다.
그러다 익명 사용자의 접속 허용 여부를 설정하는 항목에서는 유효한 자격 증명인 경우만 접속을 허용하도록 Disable anonymous access(익명사용자 접속해제)를 선택해준 뒤 Next를 클릭하고 Finish 해준다.
😘호스티드 리포지터리 생성
이제 넥서스 리포지터리에서 호스티드(Hosted) 리포지터리를 생성하기 위해 Settings 로 이동한다.
그리고 Repository 를 클릭한 후 다시 하위메뉴의 Repository를 선택한다.
그런 다음 Create repository 버튼을 클릭한다.
그러면 Repositories / Select Recipe 페이지와 Recipe 리스트가 나타난다.
이중에서 maven2(hosted)를 찾아 선택해준다.
그러면 아래처럼 리포지터리생성 화면이 나타나는데 Name 필트에 이름을 입력해준다.
나는 책의 예제와 동일한 JenkinsBookCalculatorAPI_Release 로 이름을 붙여주었다. (식별할 수 있는 유일한 이름이면 되는 것 같다.)
나머지 설정은 그대로 둔 채 스크롤을 내려 Create repository 버튼을 클릭한다. 그러 리포지터리가 생성된다.
다시 Repositories 화면으로 돌아와보면 목록에서 방금 생성된 리포지터리를 확인할 수 있다.
😎 메이븐과 넥서스 리포지터리 통합
메이븐 빌드 도구에서는 기본적으로 다음 링크(https://repo.maven.apache.org/maven2)를 프로젝트의 아티팩트(.jar) 파일이 배포되는 중앙 리포지터리로 사용한다. 이제 다음 링크를 사용하는 대신 새로 생성한 넥서스 리포지터리를 사용해 아티팩트를 배포하도록 메이븐을 설정해주자.
그러기위해 다시 API 프로젝트로 돌아와 pom.xml 파일에 넥서스 리포지터리 URL을 추가해주어야 한다.
리포지터리 URL은 아래 copy 버튼을 클릭하면 복사할 수 있다.
해당 url을 복사한 후 intelliJ로 돌아와서 pom.xml을 열어주었다.
그런 뒤 제일 아래칸에 <distributionManagement></distributionManagement>태그를 추가해 URL을 작성해준다.
넥서스 리포지터리에서 익명 접속을 허용하지 않도록 설정했으므로 settings.xml 파일에 넥서스 리포지터리의 사용자 이름과 비밀번호를 제공하도록 한다. settings.xml 파일은 ${user.home}\.m2 폴더에 존재하고 없다면 직접 생성해주면 된다.
다음과 같이 넥서스 사용자 이름과 비밀번호를 입력해주자.
주의할 점은 pom.xml 에 추가했던 distributionManagement 태그안의 id 값과 settings에 설정한 server id 값이 동일해야 한다.
메이븐에서 넥서스 리포지터리 URL과 자격 증명을 구성 완료했으니 이제 넥서스 리포지터리에서 이제 .jar 파일을 릴리즈 해보도록 한다.
명령 프롬프트를 열고 api 프로젝트 디렉터리로 이동해준다.
그리고 리소스 다운로드, 소스 코드 컴파일, 단위 테스트 코드 컴파일 및 실행, .jar의 클래스 파일 패키징 등 모든 빌드 수명 주기를 단계로 실행하는 mvn deploy 명령을 실행한다.
이때 또 나는 바로 성공하지 못하고 에러가 났다.
에러 내용을 확인해보면 어처구니가 없는 에러였다.
</settings>로 끝나는 부분에 e 라는 텍스트가 들어가있었다... settings 파일 저장시엔 잘 확인한 후 저장하도록 하자.
다시 실행해보면 정상적으로 배포가 되어야하는데 또...! 에러가 발생했다.
알고보니 내가 생성해준 저장소에서는 릴리즈(Release) 버전만 허용을 하는데 pom.xml 에 작성해놓은 버전이 1.0-SNAPSHOT 이었다...
다시 pom.xml의 version을 1.0-RELEASE 로 변경해준 뒤 다시 mvn deploy 명령어를 실행해주었더니 이번엔 빌드가 성공했다.
이제 다시 넥서스 리포지터리로 이동해 릴리즈 한 것을 확인해보자
이번엔 왼쪽 메뉴의 Browse 메뉴를 선택하고 JenkinsBookCalculatorAPI_Release 를 선택해주자
그러면 프로젝트의 .jar파일을 넥서스 리포지터리로 릴리즈한 결과를 확인할 수 있다.
이제 넥서스 리포지터리에서 .jar 를 릴리즈하는 젠킨스 프리스타일 작업을 생성해보도록 하자.
1단계: 젠킨스에서 메이븐 설정
이제 오랜만에 젠킨스 서버로 다시 돌아와 로그인해 대시보드로 이동한다. 그리고 Jenkins 관리 > Tools 로 이동하자
Maven Configuration 섹션을 확인해보면 Default settings provider 필드의 기본 값이 Use default maven settings(기본 메이븐 설정 사용)으로 이는 메이븐의 ${user.home}\.m2 에 있는 기본 settings.xml 파일을 사용한다는 뜻으로 해당 파일이 다른 경로에 있지 않는 한 변경하지 않아도 된다.
스크롤을 내려 Maven installations 버튼을 클릭하면 Add Maven 버튼 버튼이 나타나는데 버튼을 클릭하면 Maven 섹션이 확장된다.
Name 필드에 이름을 입력하고 Install Automatically(자동 설치) 체크박스를 해제한 후 MAVEN_HOME 필드에 메이븐 설치 디렉터리 경로를 지정하고 Save 버튼을 클릭한다.
나는 이전에 실습에서 추가해준 것이 있기 때문에 그대로 사용하도록 하겠다.
2단계: 깃 리포지터리 자격 증명 추가
이번엔 Jenkins 관리 > Credentials 페이지로 이동해 깃 사용자 이름과 비밀번호를 입력해 자격 증명 항목을 만든다.
이전에 GitlabCredentialsDomain 도메인을 만든적이 있으므로 여기에 Add Credentials 버튼을 클릭하여 깃랩용 자격 증명을 추가해본다.
이를위해 깃랩에서도 사용자 세팅(User settings) 화면의 Access tokens 메뉴에서 실습용으로 새로운 토큰을 생성해주었다.
자격증명 만드는 법도 이전 실습에서 다 해보았기 때문에 패스하도록 한다.
3단계: 젠킨스 대시보드에서 프리스타일 작업 생성하기
이제 대시보드로 이동해 새로운 Item 링크를 클릭한다.
name 필드에 아이템명을 입력하고 아이템 타입을 Freestyle project 로 지정해준 후 OK 버튼을 클릭한다.
Configure 페이지에서 소스코드 관리 섹션으로 내려와 Git을 선택해주고 Repository URL에 깃랩 URL을 입력한다.
Credentials에 올바른 자격증명 정보를 선택해주었다면 아래와 같은 빨간색 에러 하나 없는 화면이 나타날 것이다.
그리고 조금만 아래로 내려오면 Branches to build 섹션이 있는데 여기서 깃랩에서 master 역할의 브랜치 명을 입력해주면 된다.
스크롤을 조금만 더 내려 Build Steps 섹션으로 내려온다.
Add build step을 클릭하여 Invoke top-level Maven targets 옵션을 선택해준다.
그리고 Maven version 드롭다운 메뉴에서 1단계에서 입력했던 MyMavenConfiguration 옵션을 선택해준다.
Goal 필드에는 deploy를 입력해준 후 Save 버튼을 클릭한다.
4단계: API 프로젝트에 함수와 단위 테스트 케이스 추가
API 프로젝트에 새로운 함수와 테스트 케이스를 추가해본다.
src/main/java 폴더에서 임의로 Calculator.java 파일을 만들어 아래와 같이 함수를 추가한다.
package org.example;
public class Calculator {
public int Subtraction(int num1, int num2) {
int Res = num1 - num2;
return Res;
}
}
이제 src/test/java 폴더에 해당 기능을 검증하는 단위 테스트를 몇 개 추가해준다.
package org.example;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* Unit test for simple App.
*/
public class CalculatorTest {
/**
* Rigorous Test :-)
*/
@org.testng.annotations.Test
public void shouldAnswerWithTrue() {
Assert.assertTrue(true);
}
Calculator cal;
int Result;
@BeforeClass
public void init() {
cal = new Calculator();
}
@BeforeMethod
public void ReInitialise(){
Result = 0;
}
@Test(priority = 1, groups = {"RegressionTest"})
public void TestSubtractionWithPositiveNumbers() {
Result = cal.Subtraction(50, 10);
Assert.assertEquals(Result, 40, "Subtraction does not work with Positive Numbers");
}
@Test(priority = 2)
public void TestSubtractionWith1Positive1NagativeNumbers() {
Result = cal.Subtraction(50, -10);
Assert.assertEquals(Result, 60, "Subtraction does not work with 1 Positive and 1 Negative Numbers");
}
@AfterClass
public void Teardown() {
cal = null;
}
}
API 에 새 기능을 추가한 후 .jar 버전을 2.0으로 릴리즈할 계획이므로 pom.xml 파일에서 버전값을 1.0에서 2.0으로 변경해준다.
이제 모든 파일을 저장해준 후 단위 테스트 케이스가 성공하는지 실행해본다.
5단계: 로컬 리포지터리에서 커밋과 푸시 실행
이제 명령 프롬프트를 열어 해당 프로젝트 경로로 이동해준 뒤 새로운 변경 사항을 추적하는 브랜치를 생성해본다.
git add . 명령을 실행해서 변경 사항을 스테이징 영역에 추가해준다.
그리고 commit 명령어를 이용해 새로 만든 브랜치에서 변경 사항을 커밋해준다.
이제 push 명령을 실행하여 깃 중앙 리포지터리로 브랜치를 푸시해준다.
6단계: SubtractionFunction 브랜치를 깃랩 중앙 리포지터리의 Master 브랜치와 병합
브라우저에서 깃랩 리포지터리 페이지를 새로고침하면 좀전에 푸시한 브랜치를 확인할 수 있다.
이제 Create merge request(병합 요청 생성) 버튼을 클릭한다.
그럼 New merge request 페이지가 나타나는데 Description 항목은 선택사항으로 변경 내역의 설명을 입력하고, Assignee 섹션에 Assign to me(나에게 할당) 링크가 있다면 이 링크를 클릭해 이 병합 요청을 자신에게 할당한다.
More options(추가 옵션) 섹션에서 Delete source branch when merge request is accepted(병합 요청이 수락되면 소스 브랜치를 삭제) 체크박스가 있는데 이는 선택된 상태로 유지해준다.
그러면 Master 브랜치와 병합된 후 해당 브랜치가 중앙 리포지터리에서 삭제된다.
이제 Create merge request 버튼을 클릭해준다.
그리고 Ready to merge! 섹션에서 Merge 버튼을 클릭해 변경사항을 Master(main) 브랜치에 병합해준다.
병합이 완료되었다.
다시 해당 프로젝트 리포지터리로 가보면 SubtractionFunction 브랜치가 삭제된 것을 확인할 수 있고 파일들도 잘 반영된 것을 볼 수 있다.
이제 젠킨스로 돌아와 대시보드 작업 항목 옆에 실행 아이콘을 클릭한다.
그러면 아무 문제가 없다면 Console Output(콘솔 출력) 화면으로 이동해보면 빌드 성공하는 것을 확인할 수 있
넥서스 리포지터리에서도 2.0 버전의 .jar 파일이 올라와있는 것을 확인할 수 있다.
참고로 나는 빌드를 여러번 실패했는데 원인을 찾기위해 아이템 configure(구성) 화면으로 이동해 Build Steps의 Goals 항목에 clean deploy -X 명령어로 변경한 후 다시 실행해 주었다.
해당 명령어를 입력하면 settings.xml 을 어디서 불러오는지에서부터 로그를 더 상세하게 확인할 수 있다.
확인해보니 시스템 기본 경로의 settings.xml을 불러오는 것으로 설정을 했었는데 이유는 모르겠으나 이상한 경로에서 settings.xml을 찾는것이 원인이었다.
그래서 다시 configure(구성) 페이지로 이동하여 Build step에 Settings file, Global Settings file 에다 직접 settings.xml 경로를 넣어주니 빌드가 정상적으로 되는것을 확인할 수 있었다....
혹시나 나와 같은 문제를 겪고 있는 사람들은 직접 해당 입력 필드에 경로를 입력해보도록 하자..
+) 15번만에 성공한 빌드......
'개발 독서 스터디 > 젠킨스로 배우는 CIㆍCD 파이프라인 구축' 카테고리의 다른 글
젠킨스 CI/CD - 젠킨스 파이프라인의 이해 (8) | 2025.08.03 |
---|---|
젠킨스 CI/CD - 자바 API 릴리스를 관리하는 자동 실행 프리스타일 작업 생성 (3) | 2025.08.03 |
젠킨스 CI/CD - 메이븐으로 자바 API 프로젝트 준비 (3) | 2025.07.08 |
젠킨스 CI/CD - 젠킨스 작업의 이해 (1) | 2025.06.16 |
젠킨스 CI/CD - 사용자관리 (1) | 2025.06.16 |