Help us understand the problem. What is going on with this article?

Jenkinsの設定を最小限でJenkinsfile(Pipeline)を使う

More than 1 year has passed since last update.

概要

 Jenkins2からPipelineが標準採用されたことにより、PipelineコードをJenkinsfileに定義することで今までUIでポチポチして定義していた各種タスクをコードベースで定義できるようになった。このことよりバージョン管理との統合もしやすくなりました。

 下記ではGithub → Jenkins(JSのテスト、ソースのマージ)→ Slackへの一連の流れを簡単なサンプルと共に解説していきたいと思います。Jenkinsでの設定はプラグイン導入とジョブ作成時以外はしません!理想としてはJenkinsインストール後何もせずに使えると一番うれしいですが・・・Dockerで頑張ればいけるものでしょうか

もし、gitlabをお使いならば、gitlab runnerでシンプルにCI環境を構築できるので、検討してみるのもいいかと思います
参考として「Gitlab(Runner、Container Registry)+AnsibleでローカルにCI環境を作ってみる

環境

 Jenkinsの導入環境はAmazon Linuxになります
 各種コマンドは必要に応じて読み替えてください

手順

1. Jenkinsの導入

導入手順はこちらをご参照ください

2. Jenkinsの各初期設定

Adminのセキュリティ設定と初期プラグインのインストール等

3. プラグインの導入

  • Build Authorization Token Root(認証なしで外部からビルドをキックする)
  • Slack Notification(Slackにメッセージング)
    ※上記のプラグインは自力で頑張れば、導入不要になりますので、必要に応じて導入してください

4. Jenkinsユーザ

JenkinsのジョブはJenkinsユーザによって実行されるため、
Nodejsの導入、ssh keyの生成などをJenkinsユーザで事前に行います

# rootユーザに切替
$ > sudo su

# Jenkinsユーザに切り替え
$ > su -s /bin/bash - jenkins

5. nvmでnodejsとnpmの導入

導入方法はこちらをご参照ください
 ※バージョンは適宜変更してください

6. githubで使うssh keyの生成

生成方法はこちらをご参照ください(とりあえずパスフレーズなし)
ssh使わずにaccess tokenで接続する場合は生成不要です(下記のconfigの作成も不要)

7. ssh configの作成

GithubにはSSHで接続するため、ssh configを作成します
異なる鍵で複数のリポジトリにssh接続する場合はconfigファイルを設定すると便利です
 (同じホストだけど異なる鍵でアクセスしたい時に用いる)

# $JENKINS_HOME/.ssh (デフォルトは/var/lib/jenkins/.ssh)
# .sshフォルダがない場合は、新規作成してください
# フォルダとファイルの所有者がjenkinsになっていることを確認しましょう

$ > cd ~/.ssh/
$ > vi config
config
Host sample.github.com
  User jenkins
  Hostname github.com
  IdentityFile ~/.ssh/hogehoge_rsa

Host sample2.github.com
  User jenkins
  Hostname github.com
  IdentityFile ~/.ssh/hogehoge2_rsa

8. Githubのリポジトリのデプロイキーに登録

前述で生成したssh keyの公開鍵(hogehoge_rsa.pub)をデプロイキーに登録

書き込みを許可する場合は、「Allow write acces」にチェックしてください
この記事は書き込みを前提にしていますので、チェックを入れてください
2.jpg

9. リモートホストの登録

Jenkinsのタスク実行時に新規ホスト(Github)と接続する際にホスト追加のyes/no選択でコケるので、あらかじめ手動で接続許可しときます
新規ホストの接続許可をすべて許可するやり方もありますが、セキュリティ的に問題なので今回は手動で登録します

# configで設定したHost名でssh接続し、新規ホストを追加
# 問題なく接続できたなら.sshフォルダ配下にknown_hostsファイルが生成されていることを確認してください

$ > ssh -T git@sample.github.com

10. Jenkinsで新規ジョブの追加

Pipelineジョブを選択し、作成してください
1.jpg

11. ジョブの設定

「リモートからビルド」にチェックし、認証トークンは適当なキーワードを設定してください
GithubからWebhook時にこの認証トークンでジョブをキックします

4.jpg
PipelineのDefinitionで「Pipeline script from SCM」を選択し、SCMは「Git」、Repositorie URLにGithubのリポジトリを追加してください
3.jpg
URLのホスト名はssh configのHOSTで設定した値にしてください
例)

# ssh config

Host sample.github.com
  User jenkins
  Hostname github.com
  IdentityFile ~/.ssh/hogehoge_rsa

の場合は

git@github.com:xxxxx/repo-name.git

  ↓

git@sample.github.com:xxxxx/repo-name.git

以下のようなエラーが表示されている場合は、ssh keyの所有権、アクセス権、ホスト名を確認してください
5.jpg

12. GithubのWebhookを設定

hook先のURLには上記の認証トークンとジョブ名を付加します
認証トークンを「1234567890」で設定した場合は、jenkins hook urlでは「http(s)://jenkins host name/jenkins/buildByToken/build?ジョブ名&token=1234567890」になります
6.jpg
7.jpg
設定後右上の「Test service」で問題なく疎通することを確認してください
なお、AWSのセキュリティグループで送信元を絞っている場合は、GithubのIPを許可してあげてください
IP一覧はこちらを参照ください

13. pipelineコードを含むJenkinsfileをGithubにプッシュ

Jenkinsfileはリポジトリのルートに配置してください
Jenkinsfileの詳細は下記で解説します

14. ビルド終了後はSlackに通知

8.jpg

Jenkinsfileサンプルコード

pipelineでshell実行してあれこれをやるようなサンプルになっています(初心者なのでこのぐらいしかできていません)
shellの実行結果(returnStatus)で拾って実行の可否を判定しています
returnStdoutを用いれば実行ログを取得することもできますので、もっと細かい判定などに使えると思います
各shell実行時にカレントディレクトリがジョブのルートになるため、都度ディレクトリを移動してからコマンドを実行しています

下記のサンプルコードはこちらのリポジトリに一式ありますので、
必要な部分(リポジトリ名、ブランチ名、Slackのトークンなど)を書き換えて試してみてください

Jenkinsfile
#!groovy

def err_msg = ""
def repo_name = "jenkins-pipeline-sample"
def git_url = "git@sample.github.com:comefigo/${repo_name}.git"
def dev_branch = "dev"
def release_branch = "master"

node {
    try {
        // ソースの取得
        stage("get resource") {
            // カレントディレクトにgitリポジトリが存在するか否かの確認
            if(fileExists("./${repo_name}") && fileExists("./${repo_name}/.git")) {
                // フェッチ
                def FETCH_RESULT = sh(script: "cd ./${repo_name} && git fetch --all", returnStatus: true) == 0
                if(!FETCH_RESULT) {
                    // throw error
                    error "fetchに失敗しました"
                }
                // gitがある場合はpull
                def PULL_RESULT = sh(script: "cd ./${repo_name} && git pull --all", returnStatus: true) == 0
                if(!PULL_RESULT) {
                    error "pullに失敗しました"
                }
                // ブランチの切替
                def CHECKOUT_RESULT = sh(script: "cd ./${repo_name} && git checkout ${dev_branch}", returnStatus: true) == 0
                if(!CHECKOUT_RESULT) {
                    // throw error
                    error "checkoutに失敗しました"
                }
            } else {
                // gitがない場合はclone
                def CLONE_RESULT = sh(script: "git clone ${git_url} ${repo_name}", returnStatus: true) == 0
                if(!CLONE_RESULT) {
                    error "cloneに失敗しました"
                }
            }
        }

        // npmライブラリのインストール
        stage("install libs") {
            withEnv(["PATH+NODE=${JENKINS_HOME}/.nvm/versions/node/v6.9.5/bin/"]) {
                def NPM_RESULT = sh(script: "cd ./${repo_name} && npm install", returnStatus: true) == 0
                if(!NPM_RESULT) {
                    error "npm installに失敗しました"
                }
            }
        }

        // コードのテスト
        stage("testing code") {
            withEnv(["PATH+NODE=${JENKINS_HOME}/.nvm/versions/node/v6.9.5/bin/"]) {
                def TEST_RESULT = sh(script: "cd ./${repo_name} && npm test", returnStatus: true) == 0
                if(!TEST_RESULT) {
                    error "testに失敗しました"
                }
            }
        }

        // コードマージ
        stage("merge code") {
            // ブランチの切替
            def CHECKOUT_RESULT = sh(script: "cd ./${repo_name} && git checkout ${release_branch}", returnStatus: true) == 0
            if(!CHECKOUT_RESULT) {
                error "checkoutに失敗しました"
            }

            // マージ
            def MERGE_RESULT = sh(script: "cd ./${repo_name} && git merge ${dev_branch}", returnStatus: true) == 0
            if(!MERGE_RESULT) {
                error "マージに失敗しました"
            }

            // リモートへプッシュ
            def PUSH_RESULT = sh(script: "cd ./${repo_name} && git push origin ${release_branch}:${release_branch}", returnStatus: true) == 0
            if(!PUSH_RESULT) {
                error "プッシュに失敗しました"
            }
        }

    } catch (err) {
        err_msg = "${err}"
        currentBuild.result = "FAILURE"
    } finally {
        if(currentBuild.result != "FAILURE") {
            currentBuild.result = "SUCCESS"
        }
        notification(err_msg)
    }
}

// 実行結果のSlack通知
def notification(msg) {
    def slack_channel = "#jenkins"  // jenkinsが通知するチャネル
    def slack_domain = ""           // slackのドメイン名 https://mydomain.slack.comのmydomainの部分
    def slack_token = ""            // slackのjenkinsプラグインで取得できるtoken
    def slack_color = "good"
    def slack_icon = ""
    def detail_link = "(<${env.BUILD_URL}|Open>)"  // SlackでOpenのアンカーとして表示されます
    // ビルドエラー時にメッセージの装飾を行う
    if(currentBuild.result == "FAILURE") {
        slack_color = "danger"
    }
    def slack_msg = "job ${env.JOB_NAME}[No.${env.BUILD_NUMBER}] was built ${currentBuild.result} ${currentBuild.durationString.replace(' and counting', '')}. ${detail_link} \n\n ${msg}"
    slackSend channel: "${slack_channel}", color: "${slack_color}", message: "${slack_msg}", teamDomain: "${slack_domain}", token: "${slack_token}"
}

ステージ

pipelineの各ステージを表す

stage("stage 1") {

}

stage("stage 2") {

}

変数宣言

def hogehoge = ""

関数宣言

def hogehogefunction(val1, val2) {
  return true
}

変数を表示

""内に${変数}

def hogehoge
echo "${hogehoge}"

// オブジェクトの場合はエラーになるため、"${hogehoge}"のほうが安全と思われる
echo hogehoge

try ... catch ... finally

  try {
    // エラーをthrow
    error "my exception"
  } catch (e) {
    // オブジェクトをテキストとして表示
    echo "${e}"
  } finally {
    // send message email or slack 
  }

スクリプトの実行結果を取得

returnStatus: trueにすることでsellの実行結果を取得することできます
成功時は0、それ以外は0以外

def RESULT = sh (
    script: "do something",
    returnStatus: true
) == 0

if(RESULT) {
  // do something
} else {
  // do something
}

スクリプトの実行ログを取得

returnStdout: trueにすることでshellの実行ログを取得することができます

def STDOUT = sh (
    script: "do something",
    returnStdout: true
).trim()

echo "${STDOUT}"

ビルドの成功/失敗

ビルドプロセス内の自作の判定結果に応じてビルドの成否を明示的に設定するときに
currentBuild.resultに値を設定する
resultがFAILUREでもステージの処理は継続します

if(hoge) {
  // 成功時
  currentBuild.result = "SUCCESS"
} else {
  // 失敗時
  currentBuild.result = "FAILURE"
}

環境変数を有効化

withEnvで一時的に環境変数を有効化することができます

withEnv(["PATH+NODE=${JENKINS_HOME}/.nvm/versions/node/v6.9.5/bin/"]) {
  // do something with node
}

ファイルの有無の確認

fileExistsでファイルまたはフォルダの存在を確認できる

if(fileExists("hogehoge")) {
  echo "hogehoge exist!!!"
}

Slackで使える文字の装飾

https://api.slack.com/docs/message-formatting

各種リファレンス(重要)

これを見るまでは何ができるのか、どういった関数を呼べるのかがわからなかったが、
この存在を知ってからはリファレンスを見ながらあれこれと試せるようになったので、皆さんも是非活用してみてください
導入しているプラグインのリファレンスも載っているのでかなり役に立ちます

1.png

Piplineサンプル

https://jenkins.io/doc/pipeline/examples/

長くなりましたが、なにか不明点やツッコミ、こういう便利な使い方があるぜなどがありましたら、ぜひコメントを頂けると幸いです。

comefigo
自社クラウドサービスの開発・運用をやっています。 ここには日々のアウトプットを残したいと思います。 [Katacoda](https://www.katacoda.com/comefigo7)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした