Grafana
Grafana에서 기존 대시보드의 패널을 복사해서 새로운 대시보드에 붙여넣는 방법은 다음과 같습니다:
복사할 패널 열기
⋮ (kebab 메뉴) 클릭 → Copy → Copy Panel 선택.새 대시보드에서 붙여넣기
Add panel 버튼 클릭.⋮ 메뉴 클릭 → Paste panel 선택.복사할 패널의 JSON 확인
Inspect → Panel JSON → Copy to clipboard.새 대시보드에 붙여넣기
Add panel → Panel JSON 탭 → 기존 JSON 삭제 후 복사한 JSON 붙여넣기 → 저장.대시보드 전체를 복사하려면:
Dashboard settings → JSON model → 전체 복사.대시보드를 버전 관리하려면 Grafana의 provisioning 기능이나 Git 연동도 고려해 볼 수 있어요.
필요하면 패널 JSON 구조나 예시도 제공할 수 있어요.
===
네, 그라파나(Grafana)에서 다른 대시보드의 패널을 복사하여 새 대시보드에서 사용하는 것은 매우 효율적이고 자주 사용되는 기능입니다. 방법은 아주 간단합니다.
핵심은 원본 패널의 '공유(Share)' 메뉴에서 JSON 코드를 복사한 뒤, 새 대시보드에서 '붙여넣기(Paste)' 하는 것입니다.
Share (공유) 아이콘을 클릭합니다.Export 탭을 선택합니다.Export for sharing externally 옵션을 활성화(ON) 합니다. 이 옵션을 켜야 다른 대시보드에서도 데이터 소스(Data Source) 정보가 제대로 연결됩니다.View JSON 버튼을 클릭한 뒤, Save to file 아래에 있는 Copy to clipboard 버튼을 눌러 패널의 모든 설정이 담긴 JSON 코드를 복사합니다.Add panel (패널 추가) 버튼을 클릭합니다.Paste panel JSON 버튼을 클릭합니다. (이전 버전의 그라파나에서는 JSON 탭을 선택해야 할 수도 있습니다.)Import 버튼을 클릭합니다.이제 원본 패널과 똑같은 패널이 새 대시보드에 생성된 것을 확인할 수 있습니다.
More > Duplicate를 선택하면 즉시 동일한 패널이 바로 아래에 복제됩니다.Query 탭 하단에서 데이터 소스 드롭다운 메뉴를 클릭하여 다른 데이터 소스로 쉽게 변경할 수 있습니다.More > Create library panel을 선택하여 저장할 수 있습니다.===
Grafana에서 $namespace 변수를 사용하여 네임스페이스 기반 동적 필터링을 구현하려면, 아래 단계를 따라 설정하세요. 이 작업은 Grafana 대시보드 설정 UI에서 직접 할 수 있습니다.
Prometheus에서 네임스페이스 목록을 자동으로 가져와 드롭다운으로 제공하고, 사용자가 선택한 네임스페이스에 따라 패널 쿼리가 동적으로 필터링되도록 설정
| 항목 | 값 |
|---|---|
| Name | namespace |
| Type | Query |
| Data source | Prometheus |
| Query | label_values(kube_pod_info, namespace) |
| Include All option | (선택 사항) 체크하면 "All" 선택 가능 |
| Multi-value | (선택 사항) 여러 네임스페이스를 동시에 보고 싶다면 체크 |
sum(rate(container_cpu_usage_seconds_total{namespace="$namespace"}[5m]))
또는
kube_pod_status_phase{namespace="$namespace", phase="Running"}
💡 변수명 앞에 $를 붙이면 선택한 값이 자동으로 대입됩니다.
"All"을 선택한 경우, 내부적으로는 regex로 동작합니다.{namespace=~"$namespace"}
=~는 regex match,"~"없는=는 정확히 일치하는 값만 필터링
필요하다면 템플릿 대시보드나 예제 JSON도 제공해 드릴게요.
===
MinIO PUT Request Flow in Kubernetes (with Erasure Coding)
┌─┐
║"│
└┬┘
┌┼┐ ┌─────────────┐ ┌─────┐ ┌───────────────────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ │MinIO Service│ │Pod 1│ │Pod 2 (Coordinator)│ │Pod 3│ │Pod 4│ │Pod 5│ │Pod 6│ │Pod 7│ │Pod 8│
┌┴┐ │(ClusterIP) │ │(PV1)│ │(PV2) │ │(PV3)│ │(PV4)│ │(PV5)│ │(PV6)│ │(PV7)│ │(PV8)│
K8s Client └──────┬──────┘ └──┬──┘ └─────────┬─────────┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘
│1. PUT /bucket/objectA │ │ │ │ │ │ │ │ │
│──────────────────────>│ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ 2. Forward Request ┌┴┐ │ │ │ │ │ │
│ │─────────────────────────────────────────> │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ ╔═════════════╧═╧═════════════╗ │ │ │ │ │ │
│ │ │ ║**Erasure Coding** ░║ │ │ │ │ │ │
│ │ │ ║Object is split into shards ║ │ │ │ │ │ │
│ │ │ ║(e.g., 5 Data + 3 Parity) ║ │ │ │ │ │ │
│ │ │ ╚═════════════╤═╤═════════════╝ │ │ │ │ │ │
│ │ │ 3. Write Shard D1 │ │ │ │ │ │ │ │
│ │ │<──────────────────── │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ ────┐ │ │ │ │ │ │
│ │ │ │ │ │ 4. Write Shard P1 │ │ │ │ │ │
│ │ │ │ │ <───┘ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ 5. Write Shard D2 │ │ │ │ │ │
│ │ │ │ │ ───────────────────────────>│ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ 6. Write Shard D3 │ │ │ │ │
│ │ │ │ │ ────────────────────────────────────────────>│ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ 7. Write Shard D4 │ │ │ │ │
│ │ │ │ │ ─────────────────────────────────────────────────────────────>│ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ 8. Write Shard P2 │ │ │ │
│ │ │ │ │ ──────────────────────────────────────────────────────────────────────────────>│ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ 9. Write Shard P3 │ │ │ │
│ │ │ │ │ ───────────────────────────────────────────────────────────────────────────────────────────────>│ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ 10. Write Shard D5 │ │ │
│ │ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────>│
│ │ │ │ │ │ │ │ │ │ │
│ │ │ 11. Write ACK │ │ │ │ │ │ │ │
│ │ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─> │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ 12. Write ACK │ │ │ │ │ │
│ │ │ │ │ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ 13. Write ACK│ │ │ │ │ │
│ │ │ │ │ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ 14. Write ACK │ │ │ │ │
│ │ │ │ │ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ 15. Write ACK│ │ │ │ │
│ │ │ │ │ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ 16. Write ACK │ │ │ │
│ │ │ │ │ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ 17. Write ACK│ │ │ │
│ │ │ │ │ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │ │ └┬┘ │ │ │ │ │ │
│ │ 18. 200 OK │ │ │ │ │ │ │ │
│<─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │ │ │ │ │ │
K8s Client ┌──────┴──────┐ ┌──┴──┐ ┌─────────┴─────────┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐
┌─┐ │MinIO Service│ │Pod 1│ │Pod 2 (Coordinator)│ │Pod 3│ │Pod 4│ │Pod 5│ │Pod 6│ │Pod 7│ │Pod 8│
║"│ │(ClusterIP) │ │(PV1)│ │(PV2) │ │(PV3)│ │(PV4)│ │(PV5)│ │(PV6)│ │(PV7)│ │(PV8)│
└┬┘ └─────────────┘ └─────┘ └───────────────────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘
┌┼┐
│
┌┴┐
===
Jenkins Pipeline (Jenkinsfile)에서 비동기 명령 실행 후 상태를 주기적으로 폴링(polling) 하다가 조건을 만족하면 다음 단계로 이동하는 방식은 다음처럼 구현할 수 있습니다.
아래는 성능 테스트 후 특정 상태(예: 테스트 완료 여부)를 폴링하면서 조건을 만족하면 다음 스테이지로 넘어가는 파이프라인 예시입니다.
pipeline {
agent any
environment {
CHECK_INTERVAL = 30 // 초 단위로 폴링
MAX_ATTEMPTS = 40 // 최대 시도 횟수 (총 20분 정도)
}
stages {
stage('Run Async Job') {
steps {
echo "Starting async performance test..."
// 예: 백그라운드로 테스트 실행
sh '''
nohup ./run_performance_test.sh > test.log 2>&1 &
echo $! > test.pid
'''
}
}
stage('Poll for Completion') {
steps {
script {
def attempt = 0
def completed = false
while (attempt < env.MAX_ATTEMPTS.toInteger()) {
echo "Polling attempt ${attempt + 1}/${env.MAX_ATTEMPTS}..."
// 예: 로그나 상태파일 확인
def result = sh(script: 'grep "TEST_COMPLETE" test.log || true', returnStdout: true).trim()
if (result.contains("TEST_COMPLETE")) {
echo "Test completed successfully."
completed = true
break
}
sleep time: env.CHECK_INTERVAL.toInteger(), unit: 'SECONDS'
attempt++
}
if (!completed) {
error("Performance test did not complete within the timeout.")
}
}
}
}
stage('Process Results') {
steps {
echo "Proceeding to result analysis..."
sh './parse_results.sh'
}
}
}
}
| 구성 요소 | 설명 |
|---|---|
nohup ./run_performance_test.sh & | 테스트를 백그라운드에서 실행 |
grep "TEST_COMPLETE" | 테스트 로그에 특정 키워드가 나타나는지 확인 |
sleep | 일정 시간 대기 후 재시도 (폴링) |
error(...) | 시간 초과 시 파이프라인 실패 처리 |
MAX_ATTEMPTS * CHECK_INTERVAL | 총 대기 가능 시간 (예: 40회 x 30초 = 1200초 = 20분) |
warp로 성능 테스트 중이고 warp status 명령으로 상태를 확인한다면, 다음과 같이 바꿔주세요:def result = sh(script: 'warp status | grep "COMPLETED" || true', returnStdout: true).trim()
필요하다면:
async job을 K8s Job으로 만들고 kubectl wait으로도 대기 가능mc admin trace, mc admin info, warp status 등 MinIO 관련 명령으로도 polling 가능필요한 명령어/환경에 맞게 조정된 버전이 필요하다면 알려주세요!
===
네, Jenkinsfile에서 비동기(async) 작업을 실행하고, 특정 조건이 만족될 때까지 주기적으로 상태를 확인하는 것은 매우 흔한 패턴입니다.
가장 깔끔하고 안정적인 방법은 **timeout**과 waitUntil 스텝을 조합하여 구현하는 것입니다.
timeout: 전체 대기 시간에 제한을 두어, 작업이 무한정 기다리는 것을 방지하고 실패 처리를 할 수 있습니다.waitUntil: 내부에 있는 코드를 주기적으로 실행하며, 그 결과가 true가 될 때까지 반복합니다. 조건이 만족되면 다음 단계로 진행합니다.waitUntil을 사용한 권장 방법 (Declarative Pipeline)이 방법은 Jenkinsfile의 내장 기능을 활용하여 코드가 가장 간결하고 의도가 명확하게 드러납니다.
아래 예시는 비동기 API를 호출하여 테스트를 시작하고, 해당 테스트의 상태를 10초마다 확인하여 COMPLETED가 되면 다음 단계로 넘어가는 시나리오입니다.
pipeline {
agent any
stages {
stage('Start Performance Test (Async)') {
steps {
script {
// 비동기적으로 성능 테스트를 시작하는 API를 호출합니다.
// 이 API는 즉시 응답하지만, 테스트는 백그라운드에서 실행됩니다.
// 실행된 테스트의 ID를 'TEST_ID' 환경 변수에 저장합니다.
sh '''
TEST_ID=$(curl -s -X POST "http://api.my-test-service.com/start" | jq -r .testId)
echo "TEST_ID=${TEST_ID}" > test_id.prop
'''
// 생성된 속성 파일을 읽어 Jenkins 환경 변수로 등록합니다.
def props = readProperties file: 'test_id.prop'
env.TEST_ID = props.TEST_ID
echo "성능 테스트 시작 (Test ID: ${env.TEST_ID})"
}
}
}
stage('Wait for Test Completion') {
steps {
// ⏰ 최대 30분 동안 테스트 완료를 기다립니다.
timeout(time: 30, unit: 'MINUTES') {
script {
// ⚙️ 'waitUntil' 블록이 true를 반환할 때까지 코드를 반복 실행합니다.
waitUntil {
// 10초마다 테스트 상태를 확인하는 스크립트를 실행합니다.
echo "테스트 상태 확인 중... (Test ID: ${env.TEST_ID})"
def result = sh(
script: '''
# +e: curl이 404 등 에러 코드를 반환해도 스크립트가 즉시 중단되지 않도록 함
set +e
# API를 호출하여 상태를 확인하고, jq로 'status' 필드 값만 추출
STATUS=$(curl -s "http://api.my-test-service.com/status/${TEST_ID}" | jq -r .status)
# 추출된 상태 값을 표준 출력으로 내보냄
echo $STATUS
''',
returnStdout: true
).trim()
echo "현재 상태: ${result}"
// 상태가 'COMPLETED'이면 true를 반환하여 waitUntil 루프를 종료합니다.
if (result == 'COMPLETED') {
return true
} else if (result == 'FAILED') {
// 테스트가 실패한 경우, 즉시 빌드를 실패 처리합니다.
error("테스트 실패! (Test ID: ${env.TEST_ID})")
}
// 아직 완료되지 않았다면, 10초 대기 후 다시 시도합니다.
sleep(10)
// false를 반환하여 waitUntil이 계속 실행되도록 합니다.
return false
}
}
}
}
}
stage('Process Test Results') {
steps {
echo "🎉 테스트가 성공적으로 완료되었습니다! 결과 처리를 시작합니다."
// 여기에 테스트 결과 리포트를 생성하거나 다음 작업을 수행하는 로직을 추가
}
}
}
}
while 루프를 사용한 대체 방법waitUntil 대신 script 블록 안에서 직접 while 루프를 사용하여 동일한 로직을 구현할 수도 있습니다. 조금 더 코드가 길어지지만, 루프 내에서 더 복잡한 로직을 처리해야 할 때 유용할 수 있습니다.
// 'Wait for Test Completion' 스테이지만 변경됩니다.
stage('Wait for Test Completion') {
steps {
timeout(time: 30, unit: 'MINUTES') {
script {
def isTestComplete = false
while (!isTestComplete) {
echo "테스트 상태 확인 중... (Test ID: ${env.TEST_ID})"
def result = sh(
script: '''
set +e
curl -s "http://api.my-test-service.com/status/${TEST_ID}" | jq -r .status
''',
returnStdout: true
).trim()
echo "현재 상태: ${result}"
if (result == 'COMPLETED') {
isTestComplete = true // 루프 종료 조건 충족
} else if (result == 'FAILED') {
error("테스트 실패! (Test ID: ${env.TEST_ID})")
} else {
// 완료되지 않았으면 10초 대기
sleep(10)
}
}
}
}
}
}
| 구분 | waitUntil | while 루프 |
|---|---|---|
| 가독성 | 높음. "이 조건이 될 때까지 기다린다"는 의도가 명확히 보임. | 보통. 일반적인 프로그래밍 루프 구문으로, 추가적인 주석이 없으면 의도를 파악해야 함. |
| 구문 | 간결함. Jenkinsfile에 내장된 스텝으로, 루프 변수 관리가 필요 없음. | 조금 더 복잡함. 루프 탈출을 위한 플래그 변수(e.g., isTestComplete)를 직접 관리해야 함. |
| 핵심 로직 | 블록의 최종 반환 값이 true가 될 때까지 반복. | 조건문이 false가 될 때까지 반복. |
| 추천 | 대부분의 폴링(polling) 시나리오에 강력히 추천. | 루프 내에서 상태 값에 따라 여러 다른 변수를 설정하는 등 복잡한 제어가 필요할 때 고려. |
대부분의 경우, waitUntil을 사용하는 첫 번째 방법이 더 안정적이고 Jenkins Pipeline의 모범 사례에 가깝습니다.
===
pipeline {
agent any // kubectl이 설치되고 클러스터에 접근 가능한 에이전트를 지정해야 합니다.
environment {
// 빌드마다 고유한 Job 이름을 사용하기 위해 환경 변수 설정
JOB_NAME = "perf-test-${BUILD_NUMBER}"
}
stages {
stage('Start Kubernetes Job') {
steps {
script {
// job-template.yaml 파일을 읽어 JOB_NAME을 실제 값으로 변경한 후 job.yaml 생성
// Jenkinsfile과 함께 job-template.yaml 파일을 소스코드에 포함시켜야 합니다.
def jobManifest = libraryResource 'com/mycompany/pipelines/job-template.yaml'
jobManifest = jobManifest.replace('${JOB_NAME}', env.JOB_NAME)
writeFile file: 'job.yaml', text: jobManifest
echo "Deploying Kubernetes Job: ${env.JOB_NAME}"
sh "kubectl apply -f job.yaml"
}
}
}
stage('Wait for Job Completion') {
steps {
// ⏰ 최대 1시간 동안 Job 완료를 기다립니다.
timeout(time: 1, unit: 'HOURS') {
script {
// ⚙️ Job이 성공적으로 완료될 때까지 반복 확인합니다.
waitUntil {
echo "Checking status for Job: ${env.JOB_NAME}..."
// 'succeeded' 필드 확인
def succeededCount = sh(
script: """
# jsonpath를 이용해 .status.succeeded 필드의 값을 가져옵니다.
# Job이 아직 생성 중이거나 필드가 없으면 '0'을 반환하도록 처리합니다.
kubectl get job ${env.JOB_NAME} -o jsonpath='{.status.succeeded}' 2>/dev/null || echo 0
""",
returnStdout: true
).trim()
// 'failed' 필드 확인
def failedCount = sh(
script: """
kubectl get job ${env.JOB_NAME} -o jsonpath='{.status.failed}' 2>/dev/null || echo 0
""",
returnStdout: true
).trim()
echo "Current status - Succeeded: ${succeededCount}, Failed: ${failedCount}"
// 실패 카운트가 0보다 크면 즉시 빌드를 중단시킵니다.
if (failedCount.toInteger() > 0) {
error("Job ${env.JOB_NAME} failed!")
}
// 성공 카운트가 1 이상이면 true를 반환하여 루프를 종료합니다.
if (succeededCount.toInteger() >= 1) {
return true
}
// 아직 완료되지 않았다면, 15초 대기 후 다시 시도합니다.
sleep(15)
return false
}
}
}
}
}
stage('Cleanup and Process Results') {
// always: 이전 단계의 성공/실패 여부와 관계없이 항상 실행
post {
always {
script {
if (currentBuild.result == 'SUCCESS') {
echo "🎉 Job ${env.JOB_NAME} completed successfully! Processing results..."
// 테스트 결과 수집 및 리포팅 단계
} else {
echo "Job did not complete successfully. Performing cleanup."
}
echo "Deleting Kubernetes Job: ${env.JOB_NAME}"
sh "kubectl delete job ${env.JOB_NAME} --ignore-not-found=true"
}
}
}
}
}
}
===
pipeline {
agent any // kubectl이 설치되고 클러스터에 접근 가능한 에이전트를 지정해야 합니다.
parameters {
// 빌드 실행 시 사용자로부터 파라미터를 입력받습니다.
string(name: 'TEST_IMAGE', defaultValue: 'busybox:1.36', description: '테스트를 실행할 컨테이너 이미지')
string(name: 'TEST_COMMAND', defaultValue: 'echo "Default test command running..."; sleep 60; echo "Test finished!"', description: '컨테이너에서 실행할 테스트 명령어 (sh -c "...")')
}
environment {
// 빌드마다 고유한 Job 이름을 사용하기 위해 환경 변수 설정
JOB_NAME = "perf-test-${BUILD_NUMBER}"
}
stages {
stage('Start Kubernetes Job') {
steps {
script {
// job-template.yaml 파일을 읽어 파라미터 값으로 변경한 후 job.yaml 생성
// Jenkinsfile과 함께 job-template.yaml 파일을 소스코드에 포함시켜야 합니다.
echo "Loading job template..."
def jobManifest = libraryResource 'com/mycompany/pipelines/job-template.yaml'
echo "Injecting parameters into manifest..."
jobManifest = jobManifest.replace('${JOB_NAME}', env.JOB_NAME)
jobManifest = jobManifest.replace('${TEST_IMAGE}', params.TEST_IMAGE)
jobManifest = jobManifest.replace('${TEST_COMMAND}', params.TEST_COMMAND)
writeFile file: 'job.yaml', text: jobManifest
echo "Generated job.yaml:"
sh 'cat job.yaml' // 생성된 yaml 파일 내용 확인
echo "Deploying Kubernetes Job: ${env.JOB_NAME}"
sh "kubectl apply -f job.yaml"
}
}
}
stage('Wait for Job Completion') {
steps {
// ⏰ 최대 1시간 동안 Job 완료를 기다립니다.
timeout(time: 1, unit: 'HOURS') {
script {
// ⚙️ Job이 성공적으로 완료될 때까지 반복 확인합니다.
waitUntil {
echo "Checking status for Job: ${env.JOB_NAME}..."
// 'succeeded' 필드 확인
def succeededCount = sh(
script: """
# jsonpath를 이용해 .status.succeeded 필드의 값을 가져옵니다.
# Job이 아직 생성 중이거나 필드가 없으면 '0'을 반환하도록 처리합니다.
kubectl get job ${env.JOB_NAME} -o jsonpath='{.status.succeeded}' 2>/dev/null || echo 0
""",
returnStdout: true
).trim()
// 'failed' 필드 확인
def failedCount = sh(
script: """
kubectl get job ${env.JOB_NAME} -o jsonpath='{.status.failed}' 2>/dev/null || echo 0
""",
returnStdout: true
).trim()
echo "Current status - Succeeded: ${succeededCount}, Failed: ${failedCount}"
// 실패 카운트가 0보다 크면 즉시 빌드를 중단시킵니다.
if (failedCount.toInteger() > 0) {
error("Job ${env.JOB_NAME} failed!")
}
// 성공 카운트가 1 이상이면 true를 반환하여 루프를 종료합니다.
if (succeededCount.toInteger() >= 1) {
return true
}
// 아직 완료되지 않았다면, 15초 대기 후 다시 시도합니다.
sleep(15)
return false
}
}
}
}
}
stage('Cleanup and Process Results') {
// always: 이전 단계의 성공/실패 여부와 관계없이 항상 실행
post {
always {
script {
if (currentBuild.result == 'SUCCESS') {
echo "🎉 Job ${env.JOB_NAME} completed successfully! Processing results..."
// 테스트 결과 수집 및 리포팅 단계
} else {
echo "Job did not complete successfully. Performing cleanup."
}
echo "Deleting Kubernetes Job: ${env.JOB_NAME}"
sh "kubectl delete job ${env.JOB_NAME} --ignore-not-found=true"
}
}
}
}
}
}
===
Jenkins 파이프라인에서 비밀번호 없이 원격 노드에 SSH 접속하여 명령을 실행하려면, SSH 키 기반 인증을 사용해야 합니다. Jenkins의 pod 또는 에이전트에서 SSH를 사용할 수 있도록 설정하는 절차는 다음과 같습니다.
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""
~/.ssh/id_rsa: 개인키~/.ssh/id_rsa.pub: 공개키이 작업은 Jenkins pod 내부나 CI 환경을 구성하는 Dockerfile, ConfigMap, 또는 Init script에서 자동화할 수 있습니다.
Jenkins가 접근할 원격 노드의 계정의 ~/.ssh/authorized_keys 에 Jenkins pod의 공개키를 등록합니다.
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
💡 여러 노드에 한꺼번에 등록하려면 Ansible이나 SSH 루프를 활용할 수 있습니다.
Jenkins 파이프라인 (예: Jenkinsfile)에서 다음처럼 사용합니다:
pipeline {
agent any
stages {
stage('SSH to remote node') {
steps {
script {
sh '''
ssh -o StrictHostKeyChecking=no user@remote-node 'hostname && uptime'
'''
}
}
}
}
}
방법 A: Jenkins credential 사용 (추천)
ssh-key 비공개키 등록)withCredentials([sshUserPrivateKey(credentialsId: 'my-ssh-key-id', keyFileVariable: 'SSH_KEY')]) {
sh '''
ssh -i $SSH_KEY -o StrictHostKeyChecking=no user@remote-node "ls /"
'''
}
방법 B: SSH key를 Dockerfile에 bake 또는 Kubernetes secret으로 주입
예:
COPY id_rsa /root/.ssh/id_rsa
RUN chmod 600 /root/.ssh/id_rsa
또는 Pod YAML:
env:
- name: SSH_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: ssh-key-secret
key: id_rsa
ssh -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-i ~/.ssh/id_rsa user@host "command"
StrictHostKeyChecking=no: 호스트 키 확인 생략UserKnownHostsFile=/dev/null: 호스트 키 캐시 사용 안함id_rsa 키는 Jenkins pod에서 600 권한을 가져야 합니다.Jenkins Credentials를 사용하세요.필요하다면:
도와드릴 수 있습니다.
===