내부 Git 서버를 사용해도 CI의 흐름은 같습니다. Jenkins가 변경을 가져와 테스트하고, 성공한 커밋의 결과물을 보관하면 됩니다. 다른 점은 저장소까지의 네트워크 경로와 인증 정보를 직접 관리한다는 점이에요.
Jenkins 컨트롤러와 Linux 에이전트가 준비된 환경을 전제로 합니다. 설치는 Ubuntu Jenkins 설치 또는 Rocky Jenkins 설치, 컨트롤러·에이전트 구분은 첫 Pipeline 구성과 연결해서 읽을 수 있습니다. 이 편에서는 서버 설치를 반복하지 않습니다.
Git·GitOps 전체 연재 · 18 / 24
이 글의 목차
Gitea까지 도달하는 주체를 먼저 확인한다
15편의 localhost:13000은 관리자 PC의 터널 주소였습니다. 별도 Jenkins 에이전트에서 localhost를 사용하면 자기 자신에 접속합니다. 내부 DNS와 HTTPS 또는 SSH 주소를 준비하고, 실제 코드를 가져오는 실행 위치에서 연결을 확인합니다.
Pipeline 파일을 읽는 단계는 컨트롤러나 SCM 관련 실행 경로에서도 접근이 필요할 수 있습니다. 에이전트에서 clone만 성공했다고 모든 연결이 끝났다고 가정하지 않습니다. Jenkins의 연결 오류 로그에서 어느 노드가 실패했는지 확인합니다.
1. 저장소 읽기 전용 키를 등록한다
Gitea의 해당 저장소에 CI 전용 공개 키를 deploy key로 등록하고 쓰기 권한은 부여하지 않습니다. Jenkins Credentials에는 개인 키를 SSH username with private key 유형으로 보관하고 ID를 gitea-demo-read처럼 구분합니다. 개인 작업자의 계정 키를 그대로 재사용하지 않는 편이 회수와 감사에 유리합니다.
Jenkins Git Client의 SSH Host Key Verification 설정에서는 관리자가 검증한 호스트 키를 등록하거나, 사용하는 실행 환경의 known_hosts를 준비합니다. 검증을 끄는 옵션으로 통과시키지 않습니다. 비표준 SSH 포트를 쓰면 known_hosts의 호스트 표기도 [호스트]:포트 형식인지 확인합니다.
2. 에이전트에 필요한 도구만 둔다
이 예제의 노드 label은 linux-python-git입니다. Linux 에이전트에 Git과 Python 3가 있어야 하며 Jenkins 설정에서 동일한 label을 지정합니다. 컨트롤러에서 직접 빌드하거나 운영 서버의 Docker 소켓을 빌드 권한으로 넘길 필요는 없습니다.
# Run as the same OS user that executes the Jenkins agent.
git --version
python3 --version
환경이 준비되면 저장소 루트에 다음 Jenkinsfile을 기록합니다. 17편과 같은 greeting.py, test_greeting.py를 사용합니다. GitHub용 workflow 파일이 함께 있어도 Jenkins는 이 Jenkinsfile을 기준으로 실행합니다.
pipeline {
agent { label 'linux-python-git' }
options {
skipDefaultCheckout(true)
disableConcurrentBuilds()
timeout(time: 10, unit: 'MINUTES')
}
triggers { pollSCM('H/5 * * * *') }
stages {
stage('Checkout') {
steps {
deleteDir()
checkout scm
sh 'git rev-parse HEAD'
}
}
stage('Test') {
steps { sh 'python3 -m unittest discover -v' }
}
stage('Package') {
steps {
sh 'mkdir -p dist && git archive --format=zip --output=dist/app.zip HEAD'
archiveArtifacts artifacts: 'dist/app.zip', fingerprint: true
}
}
}
}
3. Pipeline from SCM 작업을 만든다
- Jenkins에서 새 Pipeline 작업을 만듭니다.
- Definition을 Pipeline script from SCM, SCM을 Git으로 선택합니다.
- 실제 Gitea SSH 저장소 URL과
gitea-demo-read자격 증명을 지정합니다. - Branch Specifier는
*/main, Script Path는Jenkinsfile로 설정합니다. - 저장 후 첫 빌드를 수동 실행합니다.
skipDefaultCheckout(true)로 자동 checkout과 명시적 checkout이 중복되는 것을 막습니다. 이 작업에 배정된 일회성 작업 공간에서 deleteDir()로 남은 파일을 지운 뒤 코드를 가져옵니다. 공유 파일이 있는 경로를 Jenkins 작업 공간으로 지정하지 않습니다.
로그의 git rev-parse HEAD가 의도한 커밋인지 확인합니다. 테스트가 실패하면 Package 단계로 진행하지 않아야 합니다. archiveArtifacts의 fingerprint는 Jenkins 안에서 결과물을 추적하는 기능이며 암호학적 출처 서명을 대신하지 않습니다.
폴링과 웹훅은 변경을 알리는 방식이다
예제는 H/5 * * * * 폴링으로 약 5분 간격의 분산된 시점에 SCM 변경을 확인합니다. 최초 수동 빌드에서 Jenkinsfile의 트리거 설정을 읽은 뒤 동작합니다. 변경을 알리는 속도가 중요하면 Gitea와 Jenkins 플러그인의 검증된 웹훅 경로로 전환할 수 있습니다.
웹훅은 코드를 전달하는 파일 전송이 아니라 이벤트 통지입니다. Jenkins는 여전히 저장소를 읽을 인증이 필요합니다. 수신 URL, secret 검증, 이벤트 범위, 재전송과 중복 실행을 확인하고, Gitea의 내부 주소 접근 차단을 풀기 위해 전체 사설망을 무조건 허용하지 않습니다.
Jenkins 컨트롤러를 새로 띄워 이 Pipeline을 실제 실행한 범위는 아닙니다. 예제의 Python 테스트와 Git ZIP 생성은 따로 검증했습니다. 문법과 SCM 동작은 Jenkins Pipeline 문법, Git 플러그인, SSH 호스트 키 검증을 기준으로 구성했습니다.
이전 글 · [Git·GitOps 17] GitHub Actions로 CI 연결하기: 테스트 실패에서 멈추는 흐름 · 다음 글 · [Git·GitOps 19] CI 다음의 CD: 승인·산출물 승격·이미지 digest · 연재 목록과 예제 파일

![[Git·GitOps 24] Git·CI/CD·GitOps 운영 흐름 완성하기: 장애별 판단과 복구](https://blog.maruops.com/wp-content/uploads/2026/10/maruops-git-photo-12-768x510.webp)
![[Git·GitOps 23] 비밀 값 관리: Secret의 base64와 SOPS·age 암호화](https://blog.maruops.com/wp-content/uploads/2026/10/maruops-git-photo-2-768x512.webp)
![[Git·GitOps 22] 변경 감지와 복구: 자동 Sync·selfHeal·prune·Git revert](https://blog.maruops.com/wp-content/uploads/2026/10/maruops-git-photo-10-768x576.webp)
답글 남기기