마루옵스서버 · 네트워크 · AI 기술 노트

소개문의

[Jenkins 시작하기 03] 첫 작업부터 Jenkinsfile까지: Git·에이전트·Pipeline

Checkout, Test, Package, Archive 순서와 테스트 실패 시 Stop으로 분기하는 CI 파이프라인
테스트 성공·실패에 따른 Pipeline 흐름을 설명하는 개념도 · MaruOps

설치한 Jenkins에서 명령 한 줄을 실행하는 것과 팀이 다시 사용할 자동화를 만드는 것은 조금 다릅니다. 첫 작업에서는 실행 위치부터 확인하고, 그다음에는 명령의 순서를 Git에 남겨 보겠습니다. 예제는 외부 Python 패키지가 필요 없는 덧셈 함수와 테스트 두 개입니다.

Jenkins 시작하기 · 01 Ubuntu 설치 · 02 Rocky Linux 9 설치 · 03 첫 작업과 Pipeline · 04 자동화 활용

이 글의 목차

컨트롤러는 관리하고 에이전트가 실행한다

컨트롤러가 작업을 배정하고 Linux 에이전트와 배포 에이전트가 실행 결과를 돌려주는 구조
컨트롤러는 작업을 관리하고, 도구와 권한은 실행 목적에 맞는 에이전트에 배치합니다. · MaruOps

컨트롤러는 작업 설정·실행 대기열·자격 증명을 관리합니다. 에이전트는 실제로 셸 명령을 실행하는 머신입니다. 실행기(executor)는 동시에 실행할 수 있는 작업 슬롯이고, 워크스페이스(workspace)는 그 작업이 파일을 체크아웃하고 처리하는 작업 디렉터리예요.

컨트롤러에서 빌드를 실행하면 빌드가 읽을 수 있는 파일과 컨트롤러의 민감한 파일이 같은 머신에 놓입니다. 그래서 이 실습은 별도 에이전트를 연결한 뒤 내장 노드의 실행기 수를 0으로 바꿉니다. 컨트롤러 격리 권고와 에이전트 사용 안내를 함께 참고할 수 있습니다.

1. 학습용 Linux 에이전트를 준비한다

에이전트는 컨트롤러와 다른 VM이며, 주소는 예시로 192.168.56.31을 사용합니다. VM에 SSH 서버가 준비되어 있어야 합니다. Ubuntu 또는 Rocky Linux 중 해당하는 패키지 명령 한 가지만 실행하세요.

Ubuntu 에이전트

sudo apt update
sudo apt install -y openjdk-21-jre-headless git python3

Rocky Linux 9 에이전트

sudo dnf install -y java-21-openjdk-headless git python3
sudo useradd --create-home --shell /bin/bash ciagent
sudo install -d -o ciagent -g ciagent -m 0750 /home/ciagent/work
sudo install -d -o ciagent -g ciagent -m 0700 /home/ciagent/.ssh
java -version
git --version
python3 --version

ciagent는 새 실습 계정 예시입니다. 이미 같은 이름이 있으면 계정을 다시 만들지 말고 용도와 권한을 먼저 확인합니다. 이 계정에 sudo 권한을 줄 필요는 없습니다. 컨트롤러가 사용할 SSH 키의 공개키를 이 계정의 ~/.ssh/authorized_keys에 등록하고, 파일 소유자를 ciagent, 권한을 0600으로 설정합니다.

관리 PC에서 전용 키를 만들고 공개키 파일만 에이전트로 보냅니다. 아래는 새 키 파일 이름을 jenkins-ciagent로 정한 예시입니다. 기존에 같은 파일이 있으면 덮어쓰지 말고 다른 이름을 사용하세요. 생성 도중 설정한 passphrase는 나중에 Jenkins 자격 증명에도 입력합니다.

ssh-keygen -t ed25519 -f ./jenkins-ciagent -C jenkins-ciagent
scp ./jenkins-ciagent.pub labadmin@192.168.56.31:/tmp/jenkins-ciagent.pub

에이전트 VM의 관리자 터미널에서 공개키를 등록합니다. labadmin과 IP는 자신이 SSH로 관리할 수 있는 VM의 값으로 바꿉니다.

sudo tee -a /home/ciagent/.ssh/authorized_keys < /tmp/jenkins-ciagent.pub >/dev/null
sudo chown ciagent:ciagent /home/ciagent/.ssh/authorized_keys
sudo chmod 0600 /home/ciagent/.ssh/authorized_keys
rm -f /tmp/jenkins-ciagent.pub

개인키는 Jenkins의 Credentials → SSH Username with private key에 Username ciagent, ID linux-ci-ssh로 등록합니다. Private Key의 Enter directly에 개인키 파일 내용을 입력하고, 키를 만들 때 설정한 passphrase도 함께 넣습니다. System 범위는 Jenkins 자체의 에이전트 연결에 사용할 때 적합합니다. 키와 암호를 Git에 넣지 않습니다. Jenkins 자격 증명 안내에서 종류와 사용 범위를 확인하세요.

2. SSH 에이전트를 등록한다

  1. Manage Jenkins → Plugins에서 SSH Build Agents가 설치되어 있는지 확인합니다.
  2. Manage Jenkins → Nodes → New Node에서 linux-ci-01이라는 Permanent Agent를 만듭니다.
  3. Remote root directory는 /home/ciagent/work, Labels는 linux-ci, 실행기는 학습 시작값으로 1을 지정합니다.
  4. Usage는 라벨 표현식과 일치하는 작업만 실행하는 방식으로 지정합니다.
  5. Launch method는 Launch agents via SSH, Host는 실제 에이전트 IP, Credentials는 linux-ci-ssh로 선택합니다.
  6. SSH 호스트 키 검증은 Known hosts file Verification Strategy를 사용합니다. 아래 절차로 컨트롤러의 jenkins 계정에 호스트 키를 등록한 후 연결합니다.
  7. 에이전트가 Online이 되면 내장 노드(Built-In Node)의 실행기를 0으로 설정합니다.

호스트 키는 연결 상대 서버의 신원을 확인하는 값입니다. 에이전트 콘솔에서 아래 명령으로 지문을 확인합니다.

sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

컨트롤러에서 다음 명령으로 수집한 지문이 에이전트 콘솔의 값과 같은지 대조합니다. 일치할 때만 마지막 두 줄로 known_hosts에 등록하세요. ssh-keyscan 결과를 받았다는 사실만으로 상대를 신뢰하지는 않습니다.

CI_HOSTKEY_FILE=$(mktemp)
ssh-keyscan -t ed25519 192.168.56.31 > "$CI_HOSTKEY_FILE"
ssh-keygen -lf "$CI_HOSTKEY_FILE"

# 에이전트 콘솔의 지문과 대조해 일치한 경우에만 진행
sudo install -d -o jenkins -g jenkins -m 0700 /var/lib/jenkins/.ssh
sudo tee -a /var/lib/jenkins/.ssh/known_hosts < "$CI_HOSTKEY_FILE" >/dev/null
sudo chown jenkins:jenkins /var/lib/jenkins/.ssh/known_hosts
sudo chmod 0600 /var/lib/jenkins/.ssh/known_hosts
rm -f "$CI_HOSTKEY_FILE"

SSH 방식은 컨트롤러에서 에이전트의 SSH 포트로 연결합니다. 이 예제 때문에 컨트롤러에 50000번 인바운드 포트를 따로 열 필요는 없습니다. 플러그인의 연결 방식과 검증 옵션은 SSH Build Agents 문서를 기준으로 확인했습니다.

3. Freestyle 작업으로 실행 위치를 확인한다

  1. 대시보드에서 New Item → Freestyle project로 first-agent-check를 만듭니다.
  2. 작업의 실행 위치 제한(Restrict where this project can be run)에 linux-ci를 입력합니다.
  3. Build Steps에서 Execute shell을 추가하고 아래 명령을 저장합니다.
  4. Build Now → 해당 빌드 → Console Output에서 사용자·경로·도구 버전을 확인합니다.
set -eu
whoami
hostname
pwd
java -version
python3 --version
git --version

사용자가 ciagent이고 경로가 에이전트의 work 하위라면 의도한 곳에서 실행된 것입니다. 콘솔의 성공 표시보다 먼저 이 두 값을 보세요. PC에서 되는 명령이 Jenkins에서 안 될 때는 실행 계정·PATH·현재 디렉터리가 달라진 경우가 많습니다.

4. 테스트할 작은 Git 저장소를 만든다

자신이 관리하는 Git 저장소에 다음 세 파일을 저장합니다. 테스트는 Python 표준 라이브러리 unittest만 사용하므로 pip 설치가 필요 없습니다.

jenkins-demo/
├── app.py
├── tests/
│   └── test_app.py
└── Jenkinsfile

app.py

def add(a, b):
    return a + b

tests/test_app.py

import unittest
from app import add


class AddTest(unittest.TestCase):
    def test_positive_numbers(self):
        self.assertEqual(add(2, 3), 5)

    def test_negative_numbers(self):
        self.assertEqual(add(-2, -3), -5)


if __name__ == "__main__":
    unittest.main()

저장소 루트에서 python3 -m unittest discover -s tests -v를 실행해 두 테스트가 통과하는지 먼저 확인합니다. 이 예제의 Python 파일과 패키지 생성 명령은 별도 작업 디렉터리에서 실행해 확인했습니다. Jenkins 안의 연결·권한·플러그인 조합은 자신의 VM에서 아래 절차로 확인해야 합니다.

5. Jenkinsfile에 실행 순서를 적는다

pipeline {
    agent { label 'linux-ci' }

    options {
        skipDefaultCheckout(true)
        disableConcurrentBuilds()
        timeout(time: 10, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '20'))
    }

    stages {
        stage('Checkout') {
            steps {
                deleteDir()
                checkout scm
            }
        }
        stage('Test') {
            steps {
                sh 'python3 -m unittest discover -s tests -v'
            }
        }
        stage('Package') {
            steps {
                sh 'mkdir -p dist && tar -czf dist/app.tgz app.py'
            }
        }
        stage('Archive') {
            steps {
                archiveArtifacts artifacts: 'dist/app.tgz', fingerprint: true
            }
        }
    }
    post {
        success { echo 'Tests passed; artifact archived.' }
        failure { echo 'Build failed. Check the first failing stage.' }
        cleanup { deleteDir() }
    }
}

skipDefaultCheckout(true)로 자동 체크아웃을 생략하고, Checkout 단계에서 checkout scm을 한 번 명시적으로 실행합니다. SCM은 소스 관리 저장소를 뜻해요. 이 예제는 다음 단계에서 만드는 Pipeline script from SCM 작업을 전제로 합니다. Jenkins 화면에 스크립트만 붙여 넣는 작업에서는 scm 정보가 없어 그대로 동작하지 않을 수 있습니다. Jenkinsfile 사용 문서를 함께 확인하세요.

설정이 예제에서 필요한 이유
linux-ci 라벨실행할 도구와 계정이 준비된 에이전트 선택
disableConcurrentBuilds같은 작업의 동시 실행을 제한. 서로 다른 배포 작업까지 잠그는 설정은 아님
timeout할당된 에이전트에서 실행이 무한히 이어지지 않게 제한. 최상위 timeout은 에이전트 대기까지 포함하지 않음
buildDiscarder빌드 기록·산출물이 끝없이 누적되지 않도록 보관 개수 제한
deleteDir현재 워크스페이스를 정리. Git과 보관된 산출물을 삭제하는 명령은 아님
fingerprint보관한 산출물 추적용 지문 기록. 보안 서명이나 SBOM을 대신하지 않음

옵션의 적용 위치와 문법은 Declarative Pipeline 문법에서 확인할 수 있습니다. 위 예제는 체크아웃·테스트·패키징·보관의 순서만 담았으며, 서버 배포는 포함하지 않습니다.

6. Git에서 Pipeline을 읽고 한 번 실패시켜 본다

  1. 세 파일을 Git에 커밋하고 원격 저장소에 push합니다.
  2. New Item → Pipeline으로 새 작업을 만들고 Definition을 Pipeline script from SCM으로 지정합니다.
  3. SCM에서 Git을 선택하고 자신의 저장소 URL과 브랜치를 입력합니다. 예를 들어 main 브랜치라면 */main을 사용합니다.
  4. 비공개 저장소라면 읽기 전용으로 접근할 자격 증명을 선택합니다. URL에 토큰을 직접 적지 않습니다.
  5. Script Path는 Jenkinsfile로 두고 저장한 뒤 Build Now를 실행합니다.
  6. 성공하면 빌드 페이지에서 dist/app.tgz 산출물을 확인합니다.
  7. 테스트의 기대값을 의도적으로 5에서 6으로 바꿔 커밋합니다. 다시 실행했을 때 Test에서 실패하고 새 Package·Archive 단계가 실행되지 않는지 봅니다.
  8. 기대값을 5로 되돌린 커밋을 올립니다. 이전 성공 빌드의 산출물과 현재 실패 빌드를 구분해 확인합니다.

처음에는 버튼으로 실행해 흐름을 확인하는 편이 편합니다. 자동 실행은 이 수동 실행이 통과한 뒤 붙여도 늦지 않아요. 04 자동화 활용에서는 Git 변경 감지, 컨테이너 이미지, Kubernetes 배포, 운영 점검으로 범위를 넓혀 봅니다.

댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

닉네임·이메일·댓글 내용·IP 주소·브라우저 정보는 댓글 게시 및 관리에 사용됩니다. 이메일과 접속 정보는 공개 댓글에 표시하지 않습니다. 개인정보 요청은 운영자 이메일로 보내 주세요. 개인정보처리방침