Jenkins PipelineでAnsibleを使ってファイル転送をさくっと行う

# はじめに

* Jenkinsサーバから、成果物やその他ファイルをリモートホストへ転送する方法を紹介します
* Jenkins PipelineとAnsibleを組み合わせたらシンプルに実装ができました

## 経緯
* 今まで以下の組み合わせでやっていてイケてなさすぎたので、最近のイケてるツールで実装してみたかった
    - JenkinsジョブのGUI設定 + scpでのファイル転送(expectでPASSWORD入力)

## 補足

* [Jenkinsfileとは?](https://jenkins.io/doc/book/pipeline/jenkinsfile/)
    - Jenkins2.0からビルトインで提供されている機能。今までジョブの設定はWebUIベースであったが、DSL(Groovy)を記述することでジョブの設定が可能になる

* [Ansibleとは?](https://www.ansible.com/)
    - サーバの構成管理ツール。YAML形式でタスクを定義することで様々な処理を自動化することが可能

# イメージ図

![overview.png](https://qiita-image-store.s3.amazonaws.com/0/108052/fbcf2e09-2f87-868c-9f38-514971c0cd1c.png)


# Jenkinsで使用するリポジトリの構成

* 今回はGitlabを使用していますが、GitHub,SVNなんでもよいです

* 簡単なサンプルはこちら[Github](https://github.com/tarosaiba/jenkins-pipeline-ansible-deploy)

```bash
.
jenkinsfile/
    deploy_file.groovy              # Jenkinジョブで呼び出すJenkinsfile

ansible/
    ansible.cfg
    hosts                           # リモートホストの接続情報を定義
    deploy_local_file.yml           # Ansible taskファイル

    deploy_files/                   # 転送するファイルの一時置き場
        test.txt
```

# 検証環境

* CentOS 7
* Jenkins ver. 2.73.1
* ansible 2.4.0.0

# 実行サンプル

* まずはどんな感じで使えるかをサンプルで

## Jenkinsジョブ設定

### Pipelineジョブの作成

![jenkins-job-create.png](https://qiita-image-store.s3.amazonaws.com/0/108052/5ec878cc-9d52-a84d-fe2a-4b57a16325a7.png)


### Pipeline設定

![jenkins-job-setting.png](https://qiita-image-store.s3.amazonaws.com/0/108052/b48982e3-e4b6-17cc-b909-743f93ed5b53.png)


## Jenkinsジョブ実行

* ※ Jenkinsfileでパラメータを設定する場合、一度ジョブを実行してJenkinsfileを呼び出さないとジョブの設定に反映されません

### パラメータ付ビルド

![jenkins-job-execute.png](https://qiita-image-store.s3.amazonaws.com/0/108052/60e118eb-a6f5-ee11-9391-e5b08d36bebf.png)

![jenkins-job-result.png](https://qiita-image-store.s3.amazonaws.com/0/108052/bbeb4936-092c-2b9d-124c-879be14abedc.png)


* ファイルが転送されました！

```bash
[remote_user@remote_host ~]$ ls -l /tmp/test.txt
-rw-r--r-- 1 remote_user group 5 xxx xx yy:yy /tmp/test.txt
```

ソースコードを書けばあとはジョブの設定して、実行するだけです！<br>
これから中で何が動いているのかを記載します


# 仕組み解説

* 今回の例
    * 転送するローカルファイル: test.txt
    * 転送先リモートホスト: stg の /tmp配下

## Ansible

### 実行するtask

* 今回使用するAnsibleモジュールは[copy](http://docs.ansible.com/ansible/latest/copy_module.html)モジュールです

* taskファイルは以下
    - 変数は実行時にextra-varsとして渡します

```yaml:deploy_local_file.yml
- hosts: '{{ host }}'
  tasks:
   - copy:
       src: '{{ local_path }}'
       dest: '{{ remote_path }}'
       force: yes
```

### hosts

* 複数リモートホストを定義しています。今回は環境ごとにホストを定義しました
* Jenkinsジョブのパラメータとして指定できるようにします

```bash
## prd
[prd]
192.168.10.100

[prd:vars]
ansible_ssh_port=22
ansible_ssh_user="remote_user"
ansible_ssh_pass="password"

## stg
[stg]
192.168.20.100

[stg:vars]
ansible_ssh_port=22
ansible_ssh_user="remote_user"
ansible_ssh_pass="password"

## dev
[dev]
192.168.30.100

[dev:vars]
ansible_ssh_port=22
ansible_ssh_user="remote_user"
ansible_ssh_pass="password"
```

* 今回は環境識別子でホストを定義していますが、以下のように定義することも可能です

```bash
## web server
[web]
192.168.10.200

[web:vars]
ansible_ssh_port=22
ansible_ssh_user="remote_user"
ansible_ssh_pass="password"
```


### 呼び出し方法

* 後述しますが、ansible-playbookで以下のように実行します
* extra-varsとして以下の変数をtaskファイルに渡します
    - host : hosts内で定義しているホスト
    - deploy_file: ローカル上の転送するファイルのパス
    - remote_file: 転送先のパス

```bash
ansible-playbook deploy_local_file.yml --extra-vars "host=${remote_host} local_path=${deploy_file} remote_path=${remote_path}"
```


## Jenkinsfile

* ジョブパラメータ設定
    * リモートホストを指定できるようにパラメータ化しています

```groovy:deploy_file.gvy
// ジョブパラメータ設定
properties([
  [$class: 'ParametersDefinitionProperty',
    parameterDefinitions: [
      [$class: 'StringParameterDefinition',
        name: 'REMOTE_HOST',
        //defaultValue: 'some value', デフォルト値も設定可能だが、今回は使用しない
        description: '環境識別子[prd,stg,dev]']]
  ]
])
```

* 変数定義

```groovy:deploy_file.gvy
// Define
def remote_host = "${env.REMOTE_HOST}" // get Jenkins Parameter
def deploy_file = "./deploy_files/test.txt"
def remote_path = "/tmp/"

// for git clone
def credentialsId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // Jenkinsで使用可能なCredential
def git_repo_jenkins = "jenkins-pipeline-ansible-deploy"
def git_url_jenkins = "https://github.com/tarosaiba/${git_repo_jenkins}.git" // 自環境に合わせて設定

// for get deploy file (使用しないが例として)
// def wget_url = "http://xx.xx.xx.xx:8080/job/hoge-job/lastSuccessfulBuild/artifact/fuga-artifact.tar.gz"
```

* Pipeline

```groovy:deploy_file.gvy
// Pipeline
node {
   stage('Workspase') {
      // Workspace有効化
      ws('workspace') {
      }
   }
   stage('GitClone') {
      // Jenkins repoをclone
      git credentialsId: "${credentialsId}", url: "${git_url_jenkins}"
   }
   stage('GetDeployFile') {
      // 転送用ファイルの取得
      // 本サンプルは"./deploy_files/test.txt"を使用するため何もしない
      sh ":"
      //// Sample 以下のようにJenkinsの成果物をwgetで取得することも可能
      //// sh "wget ${wget_url}  -P ./ansible/deploy_files"
   }
   stage('DeployFileToRemotehost') {
      // 指定ファイルの転送
      sh "cd ./ansible && ansible-playbook deploy_local_file.yml --extra-vars \"host=${remote_host} local_path=${deploy_file} remote_path=${remote_path}\""
   }
}
```

## 転送用ファイルについて

* 転送用ファイルは、GetDeployFileのstageにて以下のようにして準備すると便利です
    - Jenkinsジョブの成果物をwget (ソースの例)
    - 他リポジトリをJenkinsPipeline内でクローンして、指定ファイルを"./ansible/deploy_filesに移動"
    - など、色々

* 転送用ファイルのファイルパス"deploy_file"をJenkinsジョブのパラメータ化してもよいです。(例えば以下)
    * もちろん"remote_path"も可能

```
(..)

// Define
def remote_host = "${env.REMOTE_HOST}" //get Jenkins Parameter
def deploy_file = "${env.DEPLOY_FILE}" // 転送用ファイルパスのパラメータ化

(..)
```


# 最後に

* JenkinsのPipeline as Codeは、最初はなれませんでしたがGUIでやっていたことはほとんど設定可能でした
    - 慣れてしまったらこっちの方が絶対にいいです。管理がすごく楽

* Ansibleのtaskファイルで作業を定義すると、処理がかなりシンプル(YAML形式)に定義できてうれしい
    - Ansibleはサーバプロビジョニングだけではなく、普段の作業の自動化に役立てそうなことがたくさんありました