AIを使ってしたい100のこと

生成AIの可能性と、それを使って自分自身を表現する楽しさを伝えることを目指しています。

JenkinsとHelmを活用したCI/CDパイプラインの構築

はじめに

仕事でJenkinsパイプラインを開発しています。このJenkinsパイプラインは、GitLabリポジトリにあるHelmチャートをgit cloneし、そのHelmチャートをhelm upgrade --installします。

その際、Helmチャートの正常性は、どのように確認したらいいのでしょうか。生成AIを使って確認してみました。

# helm upgrade --install実行前に必要な処理

プロンプト

以下のJenkinsパイプラインhelm-install.jenkinsfileは、GitLabリポジトリにあるHelmチャートをgit cloneし、Helmチャートをhelm upgrade --installします。

このJenkinsパイプラインでhelm upgrade --installを実行する前に、helm upgrade --installが実行可能であることを確認する処理を追加したいです。そのために、何を実行するべきですか?現在私は、helm linthelm upgrade --install --dry-runを実行する処理を追加しようと考えています。

helm-install.jenkinsfile

省略

回答

helm linthelm upgrade --install --dry-runを実行する処理を追加することは、Helmチャートが正しく構成されているか、そしてhelm upgrade --installが問題なく実行できるかを確認するための良い方法です。

これらのコマンドをJenkinsパイプラインに組み込むためには、新たなステージを追加することをお勧めします。このステージでは、Helmチャートの検証を行います。以下にその例を示します:

stage('Verify Helm Chart') {
    steps {
        sh """
            helm lint ${env.HELM_CHART_PATH}
            helm upgrade --install --dry-run test-release ${env.HELM_CHART_PATH} \
            --values ${env.HELM_CHART_PATH}/${env.VALUES_FILE} \
            --namespace ${env.NAMESPACE}
        """
    }
}

このステージをSetup HelmDeploy PostgreSQLの間に追加します。これにより、Helmチャートが適切に構成されていること、そしてhelm upgrade --installが問題なく実行できることを確認できます。もし何か問題があれば、このステージでエラーが発生し、デプロイが中止されます。これにより、問題のあるHelmチャートがデプロイされることを防ぐことができます。ただし、helm upgrade --install --dry-runは実際のリソースを作成しないため、実際のデプロイ時に問題が発生する可能性はあります。それらの問題をキャッチするためには、デプロイ後のテストやヘルスチェックを行うことをお勧めします。また、helm lintはHelmチャートの構文をチェックしますが、値ファイル(values file)のエラーは検出できません。そのため、値ファイルのエラーを検出するためには別の方法を検討する必要があります。

値ファイル(values file)のエラー検出方法

プロンプト

Helmコマンドのhelm lintでは、値ファイル(values file)のエラーは検出できないと聞きました。値ファイルのエラーを検出する別の方法を教えてください。

回答

はい、その通りです。helm lintコマンドは主にチャートの構造と構文をチェックしますが、値ファイル(values file)のエラーは検出できません。

値ファイルのエラーを検出するためには、以下のような方法があります:

  1. Helmのテンプレートレンダリングを使用するhelm templateコマンドを使用して、値ファイルを適用した結果のマニフェストを生成します。これにより、値が正しく適用されているか、またはエラーが発生していないかを確認できます。以下にそのコマンドを示します:
helm template -f your-values-file.yaml your-chart-directory/
  1. Kubernetesのドライランを使用するkubectl apply --dry-run=client -fコマンドを使用して、生成されたマニフェストKubernetes APIに対して有効であることを確認します。これにより、Kubernetesリソースとして適切でない値(例えば、不適切なリソースタイプやプロパティ)が検出されます。以下にそのコマンドを示します:
helm template -f your-values-file.yaml your-chart-directory/ | kubectl apply --dry-run=client -f -

これらの方法を使用することで、値ファイルのエラーをより効果的に検出できます。ただし、これらの方法でも値の論理的なエラー(例えば、期待する範囲外の数値)は検出できないため、そのようなエラーを防ぐためには、適切なデフォルト値の設定や値の検証を行うことが重要です。また、CI/CDパイプラインにこれらのチェックを組み込むことで、エラーを早期に検出し、修正することができます。

サンプルコード

生成AIの指摘を受け、私が作成したコードが、以下の通りです。実際にhelm upgrade --installを実行する前に、3段階のチェックを設けました。

  • helm lint
    • helmチャートの構造と構文が正しいことを確認する。
  • helm template -f your-values-file.yaml your-chart-directory/ | kubectl apply --dry-run=client -f -
    • 値ファイル(values file)が正しいことを確認する。
    • 生成されたマニフェストKubernetes APIに対して有効であることを確認する。
  • helm upgrade --install --dry-run
    • helm upgrade --installの仮実行ができることを確認する。
pipeline {
    agent any

    parameters {
        string(name: 'OPENSHIFT_PROJECT', defaultValue: 'test-namespace', description: 'OpenShift project')
        string(name: 'GIT_CREDENTIALS', defaultValue: 'test-namespace-git-sshkey', description: 'GitLab credentials ID')
        string(name: 'SERVICE_ACCOUNT_NAME', defaultValue: 'jenkins-dbaas-test-namespace', description: 'serviceAccountName')
    }

    environment {
        HELM_CHART_PATH = '.'
        VALUES_FILE = 'guest-values.yaml'
        NAMESPACE = "${params.OPENSHIFT_PROJECT}" // デプロイ先のKubernetes名前空間
        SERVICE_ACCOUNT_NAME = "${params.SERVICE_ACCOUNT_NAME}"
    }

    triggers {
        gitlab(
            triggerOnPush: true,
            triggerOnMergeRequest: false,
            branchFilterType: 'All',
            includeBranchesSpec: 'master'
        )
    }

    stages {
        stage('Extract Parameters') {
            steps {
                script {
                    echo "OpenShift Project: ${env.OPENSHIFT_PROJECT}"
                    echo "Git Credentials: ${env.GIT_CREDENTIALS}"
                    echo "Service Account Name: ${env.SERVICE_ACCOUNT_NAME}"
                }
            }
        }

        stage('Clone Repository') {
            steps {
                script {
                    git branch: 'master',
                        url: "${gitlabSourceRepoSshUrl}",
                        credentialsId: "${params.GIT_CREDENTIALS}"
                }
            }
        }

        stage('Setup Helm') {
            steps {
                sh 'helm version'
            }
        }

        stage('Helm Lint') {
            steps {
                script {
                    def lintResult = sh(script: "helm lint ${env.HELM_CHART_PATH}", returnStatus: true)
                    if (lintResult != 0) {
                        echo "Helm lint failed with exit code: ${lintResult}"
                        currentBuild.result = 'FAILURE'
                        error("Helm lint failed")
                    }
                }
            }
        }

        stage('Check Helm Template Validity') {
            steps {
                script {
                    def templateResult = sh(script: "helm template . -f ${env.VALUES_FILE} --namespace ${env.NAMESPACE} | oc apply --dry-run=client -f - --namespace ${env.NAMESPACE}", returnStatus: true)
                    if (templateResult != 0) {
                        echo "Helm template failed with exit code: ${templateResult}"
                        currentBuild.result = 'FAILURE'
                        error("Helm template failed")
                    }
                }
            }
        }

        stage('Get Helm Release Name') {
            steps {
                script {
                    def releaseName = sh(script: "helm template . -f ${env.VALUES_FILE} --show-only templates/cluster.yaml --namespace ${env.NAMESPACE} | oc apply --dry-run=client -f - --namespace ${env.NAMESPACE} -o json | jq -r '.metadata.name'", returnStdout: true).trim()
                    env.RELEASE_NAME = releaseName
                    echo "Release Name: ${env.RELEASE_NAME}"
                }
            }
        }

        stage('Helm Dry Run') {
            steps {
                script {
                    def dryRunResult = sh(script: """
                        helm upgrade --install ${env.RELEASE_NAME} ${env.HELM_CHART_PATH} \
                        --values ${env.HELM_CHART_PATH}/${env.VALUES_FILE} \
                        --namespace ${env.NAMESPACE} --dry-run
                    """, returnStatus: true)
                    if (dryRunResult != 0) {
                        echo "Helm dry run failed with exit code: ${dryRunResult}"
                        currentBuild.result = 'FAILURE'
                        error("Helm dry run failed")
                    }
                }
            }
        }

        stage('Helm Install') {
            steps {
                script {
                    sh """
                        helm upgrade --install ${env.RELEASE_NAME} ${env.HELM_CHART_PATH} \
                        --values ${env.HELM_CHART_PATH}/${env.VALUES_FILE} \
                        --namespace ${env.NAMESPACE}
                    """
                }
            }
        }
    }

    post {
        success {
            echo 'PostgreSQL deployed successfully!'
        }
        failure {
            echo 'Deployment failed!'
        }
    }
}

やってみた感想

  • 「これらの方法を使用することで、値ファイルのエラーをより効果的に検出できます。」
    • 完璧ではないですが、まあまあいい感じの事前チェックができたのではと思います。
  • 「ただし、これらの方法でも値の論理的なエラー(例えば、期待する範囲外の数値)は検出できないため、そのようなエラーを防ぐためには、適切なデフォルト値の設定や値の検証を行うことが重要です。」
    • 時間があったら、この件も調べて対応したいと思います。
  • helm template -f your-values-file.yaml your-chart-directory/ | kubectl apply --dry-run=client -f -
    • 一度マニフェストを生成し、applyし、その結果によってHelmチャートの正常性を確認する、というのはなかなかのテクニックだと思いました。
  • helm upgrade --install --dry-runは実際のリソースを作成しないため、実際のデプロイ時に問題が発生する可能性はあります。それらの問題をキャッチするためには、デプロイ後のテストやヘルスチェックを行うことをお勧めします。」
    • 今回は追加していませんが、「デプロイ後のテスト」は必要な処理だと思います。生成AIによって気づかされた点です。