test0807b

Young-Kyoo Kim·2025년 8월 6일

Grafana

Grafana에서 기존 대시보드의 패널을 복사해서 새로운 대시보드에 붙여넣는 방법은 다음과 같습니다:


방법 1: UI를 통한 복사/붙여넣기

  1. 복사할 패널 열기

    • 복사하려는 패널 우측 상단의 (kebab 메뉴) 클릭 → CopyCopy Panel 선택.
  2. 새 대시보드에서 붙여넣기

    • 새 대시보드 또는 원하는 대시보드를 열고 Add panel 버튼 클릭.
    • 열리는 창의 우측 상단 메뉴 클릭 → Paste panel 선택.

방법 2: JSON 수동 복사

  1. 복사할 패널의 JSON 확인

    • 기존 대시보드 편집 → 상단 메뉴 InspectPanel JSONCopy to clipboard.
  2. 새 대시보드에 붙여넣기

    • 새 대시보드 → Add panelPanel JSON 탭 → 기존 JSON 삭제 후 복사한 JSON 붙여넣기 → 저장.

추가 팁

  • 대시보드 전체를 복사하려면:

    • 기존 대시보드에서 Dashboard settingsJSON model → 전체 복사.
    • 새 대시보드 생성 시 해당 JSON 붙여넣기 가능.
  • 대시보드를 버전 관리하려면 Grafana의 provisioning 기능이나 Git 연동도 고려해 볼 수 있어요.

필요하면 패널 JSON 구조나 예시도 제공할 수 있어요.

===

네, 그라파나(Grafana)에서 다른 대시보드의 패널을 복사하여 새 대시보드에서 사용하는 것은 매우 효율적이고 자주 사용되는 기능입니다. 방법은 아주 간단합니다.

핵심은 원본 패널의 '공유(Share)' 메뉴에서 JSON 코드를 복사한 뒤, 새 대시보드에서 '붙여넣기(Paste)' 하는 것입니다.


패널 복사 및 붙여넣기 단계별 가이드

  1. 원본 패널에서 '복사'하기
    • 복사하고 싶은 패널이 있는 원본 대시보드로 이동합니다.
    • 복사할 패널의 제목(Title) 부분을 클릭하면 나타나는 메뉴에서 Share (공유) 아이콘을 클릭합니다.
    • 팝업 창이 뜨면 상단의 Export 탭을 선택합니다.
    • Export for sharing externally 옵션을 활성화(ON) 합니다. 이 옵션을 켜야 다른 대시보드에서도 데이터 소스(Data Source) 정보가 제대로 연결됩니다.
    • View JSON 버튼을 클릭한 뒤, Save to file 아래에 있는 Copy to clipboard 버튼을 눌러 패널의 모든 설정이 담긴 JSON 코드를 복사합니다.
  1. 새 대시보드에 '붙여넣기'
    • 패널을 붙여넣고 싶은 새로운(또는 대상) 대시보드로 이동합니다.
    • 대시보드 오른쪽 상단에 있는 Add panel (패널 추가) 버튼을 클릭합니다.
    • 다양한 시각화 유형을 선택하는 화면이 나타나면, 오른쪽 상단에 있는 Paste panel JSON 버튼을 클릭합니다. (이전 버전의 그라파나에서는 JSON 탭을 선택해야 할 수도 있습니다.)
    • 텍스트 상자가 나타나면, 1번 단계에서 복사했던 JSON 코드를 그대로 붙여넣습니다.
    • Import 버튼을 클릭합니다.

이제 원본 패널과 똑같은 패널이 새 대시보드에 생성된 것을 확인할 수 있습니다.


💡 추가 팁 (Tips & Tricks)

  • 패널 복제 (Duplicate): 만약 같은 대시보드 내에서 패널을 복사하고 싶다면, 더 간단한 방법이 있습니다. 패널 제목 메뉴에서 More > Duplicate를 선택하면 즉시 동일한 패널이 바로 아래에 복제됩니다.
  • 데이터 소스 변경: 복사된 패널의 데이터 소스를 바꾸고 싶다면, 패널 편집 화면의 Query 탭 하단에서 데이터 소스 드롭다운 메뉴를 클릭하여 다른 데이터 소스로 쉽게 변경할 수 있습니다.
  • 패널 라이브러리 (Panel Library) 활용: 자주 사용하는 패널은 '패널 라이브러리'에 저장해두면 JSON을 복사할 필요 없이 언제든지 쉽게 불러와 재사용할 수 있습니다. 패널 제목 메뉴에서 More > Create library panel을 선택하여 저장할 수 있습니다.

===

Grafana에서 $namespace 변수를 사용하여 네임스페이스 기반 동적 필터링을 구현하려면, 아래 단계를 따라 설정하세요. 이 작업은 Grafana 대시보드 설정 UI에서 직접 할 수 있습니다.


🎯 목표

Prometheus에서 네임스페이스 목록을 자동으로 가져와 드롭다운으로 제공하고, 사용자가 선택한 네임스페이스에 따라 패널 쿼리가 동적으로 필터링되도록 설정


✅ 단계별 설정

1. 변수 생성

  1. 대시보드 상단 메뉴에서 "Dashboard settings" (기어 아이콘 ⚙️) 클릭
  2. 왼쪽 메뉴에서 "Variables" 클릭 → "Add variable"

2. 변수 구성

항목
Namenamespace
TypeQuery
Data sourcePrometheus
Querylabel_values(kube_pod_info, namespace)
Include All option(선택 사항) 체크하면 "All" 선택 가능
Multi-value(선택 사항) 여러 네임스페이스를 동시에 보고 싶다면 체크

3. 저장

  • 아래쪽의 "Update" 클릭 → 대시보드 오른쪽 상단에서 "Apply" 클릭하여 반영

4. 패널 쿼리에 변수 적용

  1. 원하는 패널의 편집 모드로 진입
  2. 쿼리 필터에 아래처럼 변수 사용:
sum(rate(container_cpu_usage_seconds_total{namespace="$namespace"}[5m]))

또는

kube_pod_status_phase{namespace="$namespace", phase="Running"}

💡 변수명 앞에 $를 붙이면 선택한 값이 자동으로 대입됩니다.


5. (선택 사항) All 옵션 사용 시 주의

  • "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) 하다가 조건을 만족하면 다음 단계로 이동하는 방식은 다음처럼 구현할 수 있습니다.


✅ Jenkinsfile 구조 예시

아래는 성능 테스트 후 특정 상태(예: 테스트 완료 여부)를 폴링하면서 조건을 만족하면 다음 스테이지로 넘어가는 파이프라인 예시입니다.

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)
                    }
                }
            }
        }
    }
}

비교

구분waitUntilwhile 루프
가독성높음. "이 조건이 될 때까지 기다린다"는 의도가 명확히 보임.보통. 일반적인 프로그래밍 루프 구문으로, 추가적인 주석이 없으면 의도를 파악해야 함.
구문간결함. 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를 사용할 수 있도록 설정하는 절차는 다음과 같습니다.


✅ 1. Jenkins pod에 SSH 키 생성 또는 등록

🔹 A. 새로운 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에서 자동화할 수 있습니다.


✅ 2. 원격 노드에 공개키 등록

Jenkins가 접근할 원격 노드의 계정의 ~/.ssh/authorized_keys 에 Jenkins pod의 공개키를 등록합니다.

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

💡 여러 노드에 한꺼번에 등록하려면 Ansible이나 SSH 루프를 활용할 수 있습니다.


✅ 3. Jenkins에서 SSH 접속 테스트

Jenkins 파이프라인 (예: Jenkinsfile)에서 다음처럼 사용합니다:

pipeline {
  agent any

  stages {
    stage('SSH to remote node') {
      steps {
        script {
          sh '''
            ssh -o StrictHostKeyChecking=no user@remote-node 'hostname && uptime'
          '''
        }
      }
    }
  }
}

✅ 4. Jenkins pod에 SSH 비밀키 미리 넣기

방법 A: Jenkins credential 사용 (추천)

  • Jenkins UI → Credentials → 새로 생성 (예: ssh-key 비공개키 등록)
  • Jenkinsfile에서 사용:
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

✅ 5. SSH 관련 옵션 추가 팁

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 권한을 가져야 합니다.
  • Git에 절대 비공개 키를 커밋하지 마세요.
  • 가능하면 Jenkins Credentials를 사용하세요.

필요하다면:

  • 여러 노드 병렬 SSH
  • SSH + SCP 사용
  • key 배포 자동화 스크립트

도와드릴 수 있습니다.

===

0개의 댓글