こんにちは。
技術ブログは、2回目の執筆になります。
キャスレーコンサルティングLS(リーディング・サービス)部の小寺です。

「CI/CD環境構築・活用」をテーマに紹介を行います。
今回はそのはじめとして、CI/CDの説明およびGoogle Cloud Platform(GCP)での構築例を
紹介しようと思います。

CI/CDとは

定義

一言でまとめると、

CI(Continuous Integration):継続的インテグレーション

ビルドやテストを継続的に実行すること

CD(Continuous Delivery):継続的デリバリー

CIに加えて、デプロイまでを継続的に実行すること

となります。

継続的

この文脈であれば、「自動的」と読み替えて構いません。

つまり、CI/CDとは
自動的にビルドやテスト、デプロイを行うこと(行ってくれること)
という解釈ができます。

もちろん、CI/CDにはそれぞれ深い意味や背景があり、この一言だけで済ませるわけにはいきませんが
今回は、この主要な面に絞って紹介していきます。

CI/CDの利点

ここまで単純化されたものであれば、すぐに利点がわかると思われます。
そう、作業の効率化です。

この例として、「自動化していない場合」「自動化した場合」を会話形式でご紹介いたします。

Aさん:ソースの修正者
Bさん:Aさんの上司、動作確認者
Aさんはソースの修正を依頼され、一通り修正が完了しました。

自動化していない場合

A「修正出来ました。」
B「それ動くの?」
A「ビルドを実行します。」
(ビルドコマンドを実行)
A「ビルドが通りました。」
B「問題ない?」
A「それでは、テストを実行します。」
(テストを実行)
A「テストが通りませんでした。」
B「え?今回の修正でテストが通らなくなったの?」
A「それでは、前の修正に遡ってビルド・テストしてみます。」
(修正一つずつ遡り、ビルド・テストを実行)
A「あ、一つ前の修正で、実はテストが通らなくなっていました。」
B「そこから直してみて。」
(前回の修正分にまで遡って、何処を修正したのか探し出し、修正)
A「出来ました。テストも実行して通りました。」
B「OK。次は、動いているとこ見たいんだけど。」
A「それでは、試験環境用にビルドしてデプロイしてきます。」
(構築手順に従い試験環境にデプロイ)
A「出来ました。ここで見られます。」
B「OK。動かしてみるね。」

自動化した場合

A「修正できてビルドは通ったけど、テストが通っていないや。
  前回のテストは通っているから、この修正で通らなくなってしまったのだな、直そう。」
(今回手を入れた所を修正)
A「今度こそ修正できた。テストも通ったぞ。」
(上司のもとへ移動)
A「修正できました。ビルドもテストも通っています。」
B「OK。次は、動いているところを見たいんだけど。」
A「今回の修正分は、ここにもうデプロイされています。」
B「OK。動かしてみるね。」

会話のやり取りから分かるように、作業が効率化されていますね。

それ以外の利点

上では、途中に試験環境へのデプロイを行っていますが、
ここを手動でやるのと、自動でやるのも大きな差です。
なぜなら、手作業を行うと、作業ミスが起きてしまう可能性があるからです。

試験環境用にビルド、デプロイしたつもりが、
本番環境用のリソースが入っていた、というケースも可能性もあります。
それが、殆どなくなります。

GCPでのCI/CDの構築

それでは、実際にGCPでCI/CD環境を構築してみましょう。
今回は、GCPのサービスのみで完結する
Google Kubernetes Engine(GKE)への自動デプロイ環境構築をしてみます。

今回のゴール(3行で)

eclipse上でコミット・プッシュした、SpringのHello Worldが
GCP上で自動的にビルド・デプロイされて
最新の結果を見ることができる状態にする

GCPで環境構築する理由・メリット

GCPには、Source RepositoryというGitのプライベートリポジトリがあり、
自らサーバーを立てる必要なく、公開されないGitリポジトリを簡単に作成することができます。
また、そのリポジトリへのプッシュをトリガにしたCI/CDの設定も
GUI上で簡単に可能(もちろんSDKを用いCUIでも可能)のため、入門編として採用します。

おおまかな流れ

  1. eclipse上に、Springプロジェクトの作成
  2. GCPのSource Repositoryで管理
  3. Google Cloud Buildで、自動的にビルドできる環境までまずは整えておく
  4. デプロイするGKEの環境を作る
  5. 手動でデプロイできることを確認する
  6. 自動でデプロイまでされるよう設定を変更する
  7. 実際にソースを修正して、自動的に反映されることを確認する

用意するもの

今回、以下につきまして、各自すでに用意してあるものとします。

  • GCPのアカウント及び各種登録
  • GCPのプロジェクトの作成
  • eclipseの準備(EGit含む)

構築手順

※一部、パスワード等が記載されている部分を、黒塗りにしています

eclipse上に、Springプロジェクトの作成

  1. eclipseに新規プロジェクトを作成する。
    今回は、Spring bootでHello world.します
  2. ソースを用意したら、ひとまずローカルで確認する。
    HelloApplication.java

    package com.example.hello;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class HelloApplication {
    	// 起動
    	public static void main(String[] args) {
    		SpringApplication.run(HelloApplication.class, args);
    	}
    }
    

    HelloController.java

    package com.example.hello.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    	// 「/」に対して「Hello World.」と返すようにする
    	@RequestMapping(value = "/")
    	public String hello() {
    		return "Hello World.";
    	}
    }
    
    



  3. 作成したソースを、Gitでローカルリポジトリに共有する。
    プロジェクトを右クリックし、チーム→プロジェクトの共用をクリックする。

  4. プロジェクトの親フォルダ内のリポジトリを使用、または作成のチェックをつける。

GCPのSource Repositoryで管理

  1. GCPのSource Repositoriesを開く。
  2. リポジトリを有効にする。
  3. リポジトリ名を付けて作成する。
  4. 「ローカル Git リポジトリから、Cloud リポジトリへのコードの push」の
    「Git 認証情報の生成と保存」から認証情報を生成する。



    表示されたコードの下のほうを実行する。

    $ powershell -noprofile -nologo -command Write-Output "source.developers.google.com`tFALSE`t/`tTRUE`t~~~`to`t~~~~~~" >>"%USERPROFILE%\.gitcookies"
    
  5. eclipseのGitの設定に追加する。
    「ウィンドウ」>「設定」から「チーム」>「Git」>「構成」でユーザー設定に
    http.cookiefile=%USERPROFILE%\.gitcookies
    を追加する。
  6. 「Git」パースペクティブで、リモートを追加する。



  7. プッシュする。



  8. GCPから、ソースを確認する。
  9. GCPのContainer Registryを開く。

Google Cloud Buildで自動的にビルドできる環境を整える

  1. Container Registry APIを、有効にする。
  2. GCPのCloud Buildを開く。
  3. Container Registry APIを有効にする。
  4. トリガを作成する
    今回は、「master」ブランチがプッシュされるたびに実行されるようにします。
    また、ビルド設定は「/cloudbuild.yaml」で行うものとします。




  5. ビルド設定などをプロジェクトに追加し、プッシュする。
    ひとまずビルドし、イメージをContainer Registryにプッシュする状態で
    コミット→プッシュします。
    pom.xml(Spring bootが作ったままです)

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    
    	<groupId>com.example</groupId>
    	<artifactId>hello</artifactId>
    	<version>0.0.1</version>
    	<packaging>jar</packaging>
    
    	<name>hello</name>
    	<description>Demo project for Spring Boot</description>
    
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.0.3.RELEASE</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    
    	<properties>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    		<java.version>1.8</java.version>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    
    </project>
    

    Dockerfile(起動して、8080ポートを開放する記述をします)

    FROM openjdk:8-jdk
    ADD target/hello-0.0.1.jar hello-0.0.1.jar
    ENV JAVA_OPTS=""
    ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar hello-0.0.1.jar" ]
    EXPOSE 8080
    

    cloudbuild.yaml(ビルドの実行→dockerイメージの作成→Container registryに登録する記述をします)

    steps:
    - name: 'gcr.io/cloud-builders/mvn'
    args: ['install']
    - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '--tag=gcr.io/$PROJECT_ID/cicd-test:$BRANCH_NAME-$REVISION_ID', '.']
    - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/$PROJECT_ID/cicd-test:$BRANCH_NAME-$REVISION_ID']
    

    変更したソースを、コミット+プッシュする。

  6. Gitにコミット・プッシュして、イメージができることを確認します。
    ビルドの実行結果

    Container Registryに、イメージが追加されています。

デプロイするGKEの環境を作る

  1. GCPのKubernetes Engineを開く。
  2. クラスタを作成する。
  3. 名前、ゾーン、マシンタイプ等を適宜設定し、「作成」を押下する。

手動でデプロイできることを確認

  1. クラスタの設定をデプロイする。




  2. デプロイを公開するサービスを作成する。



  3. 公開されたアドレスにアクセスし、確認する。

自動でデプロイまでされるよう設定を変更

  1. Cloud Builderのサービスアカウントに、GKEにアクセスするロールを付与する。





  2. cloudbuild.yamlを編集し、デプロイまで行うようにして再度プッシュする。
    cloudbuild.yaml
    (Container Registryに登録する処理の後にGKEのイメージを差し替える処理を追加します)

    steps:
    - name: 'gcr.io/cloud-builders/mvn'
      args: ['install']
    - name: 'gcr.io/cloud-builders/docker'
      args: ['build', '--tag=gcr.io/$PROJECT_ID/cicd-test:$BRANCH_NAME-$REVISION_ID', '.']
    - name: 'gcr.io/cloud-builders/docker'
      args: ['push', 'gcr.io/$PROJECT_ID/cicd-test:$BRANCH_NAME-$REVISION_ID']
    - name: 'gcr.io/cloud-builders/gcloud'
      entrypoint: 'bash'
      args:
      - '-c'
      - |
        gcloud container clusters get-credentials cicd-test --zone us-central1-a
        kubectl set image deployment/cicd-test cicd-test=gcr.io/$PROJECT_ID/cicd-test:$BRANCH_NAME-$REVISION_ID
    

実際にソースを修正して、自動的に反映されることを確認する

  1. 画面の表示内容を変更して、コミット・プッシュする。
    HelloController.java

    package com.example.hello.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    	@RequestMapping(value = "/")
    	public String hello() {
    		return "Hello CI/CD World.";
    	}
    }
    
    
  2. 公開されたアドレスにアクセスし、確認する。
    ※メモリ量の小さいインスタンスで、連続してプッシュすると
     反映まで時間がかかることがあります

まとめ

今回は、GCPだけで完結するCI/CD環境を構築しました。
CI/CD用の別のソフトウェアや、サービスを併用することなどで、
さらに簡単に、多岐にわたる開発の効率化が可能です。

より良い開発ができましたら幸いです。
ご一読頂き、ありがとうございます。

小寺 正毅
CSVIT事業部 LS(リーディングサービス)部 小寺 正毅
プライベートリポジトリが使えるGCPは便利ですね。