IntelliJ IDEAを使ったGo言語のセットアップ

Go言語のlocalへのインストール方法からIntelliJ IDEAへのプロジェクトのインポートと設定について記述します。

Go言語のインストール

Go言語のインストールにはgoenvを使用します。

$ git clone https://github.com/syndbg/goenv.git ~/.goenv

bashを使用している場合は以下のコマンドを実行して .bash_profile に設定を追加します。

$ echo 'export GOENV_ROOT="$HOME/.goenv"' >> ~/.bash_profile
$ echo 'export PATH="$GOENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(goenv init -)"' >> ~/.bash_profile

Go言語をインストールします。

$ goenv install 1.10.4
$ goenv local 1.10.4
$ go version
go version go1.10.4 darwin/amd64

InteliJ IDEAにGoプラグインをインストール

Configure -> Plugins をクリックします。

image

Browse repositories をクリックします。

image

Go プラグインをインストールします。

image

インストールが完了したら、IntelliJ IDEAをリスタートします。

InteliJのプロジェクトのインポートとGoの設定

Import Project からプロジェクトを選択してを開きます。

image

image

Create project from existing sources にチェックして、 Next をクリックします。

image

Next をクリックします。

image

Next をクリックします。

image

Finish をクリックします。

image

これでプロジェクトのインポートが完了します。

IntelliJ IDEAのGo言語の設定

GOROOTとGOPATHの設定

GOROOTGOPATH を確認します。

$ go env
...
GOROOT="/Users/user_name/.goenv/versions/1.10.4"
GOPATH="/Users/user_name/go"
...

Languages & Frameworks -> Go -> GOROOT を開きます。

image

GOROOTを指定します。パスを入力するにはShift + Cmd + Gでダイアログを表示することができます。

image

Languages & Frameworks -> Go -> GOPATH を開きます。

image

Project GOPATHGOPATH を指定します。パスを入力するにはShift + Cmd + Gでダイアログを表示することができます。

image

機械学習システムのアーキテクチャ設計

アーキテクチャの考え方

Machine Learning Systemsを読みました。ここで紹介されているアプリケーションのアーキテクチャの設計方法を紹介します。
こちらの書籍ではScala, Akka, Sparkを使ってリアクティブなアプリケーションを作成していますが、ここではそれ以外の一般的な部分をメモします。また、機械学習に必要なフィーチャーエンジニアリングやモデルの学習方法、モデルの評価方法も紹介されていますが、ここでは扱いません。

6割程度に目を通しましたが、Machine Learning Systemsに書かれている内容はMachine Learningにかかわらず一般的な内容が多いかと思います。

コンポーネント

ここで扱う機能としてはデータ収集、特徴の生成、モデルの学習、モデルの評価、モデルのパブリッシュ、モデルを使った予測の6つに分けられます。この各機能をコンポーネントと呼ぶことにします。

ここで紹介するのは以下の図の赤枠で囲まれたデータ収集、モデルのパブリッシュ、モデルを使った予測の部分です。

image

これらのコンポーネントを組み合わせたアプリケーションのアーキテクチャを安定して動作させるために、以下の要件が必要になってきます。

  • 他のシステムに問題が起きても予想システムは動き続けて欲しい
  • 高負荷やエラーが起きても動き続けて欲しい

これらを整理すると以下の4つの考え方に分けられます。

Responsive

一定の時間内にレスポンスを返すことができる。

Resilient

ハードウェアやヒューマンエラー、設計上の欠陥があった時も適切な応答を返せるようにする。503 (Service unavailable)なども含めて。

Elastic

負荷にかかわらずレスポンスを返すことができる。

Message Driven

非同期で処理ができる。疎結合な状態を作ることができる。システムエラーや負荷の問題を検知するのが容易。

可用性を上げる戦略

以下のような戦略で可用性を上げていきます。

レプリケーション (Responsive, Resilient)

同じコンポーネントが複数ある状態を作ります。また、データも冗長化されて保存と処理が実行されると良いでしょう。

疎結合 (Message Driven)

コンポーネント間を疎結合にします。ポイントは連鎖的にシステムが止まってしまうのを防ぐことです。コンポーネントはDockerなどを利用してコンテナ化し、マイクロサービス化するのが1つの方法としてあると思います。

Supervision (Message Driven)

各コンポーネントの死活監視をします。

Reactiveなシステム

Scala, Akka, Sparkが紹介されていましたが、他言語でActorモデルを使用するなど他の方法を使用しても良いと思います。

考慮すること

  • Scalaは不確実性に対しての考え方を提供してくれる
  • Option, Future…
  • Futureのタイムアウトは応答性を上げる。応答時間を決めて切ってやる
  • Akkaのactor modelはfailureに対していくつかの防御策を持っている
  • メッセージパッシングによってシステムの過負荷を抑制する
  • スーパーバイザーによってコンポーネントの可用性を上げる
  • Sparkはデータ処理の便利なパイプラインを提供してくれる
  • パイプラインは純粋関数とimmutableな変換で構成されている
  • 遅延評価を使用している
  • MLLibはモデルの構築と評価に便利なツールを提供してくれる。ミニマムなコードで書ける
  • 分散データベースにCouchbaseを使っていた

データ収集

既存のDashboardなどのデータを使うと思いますが、以下のようなことを念頭に集める必要があります。

  • 未加工の生データを集める
  • データは常にimmutable
  • データのアグリゲーション
  • 単一障害点

未加工の生データを集める

生データを保存するようにします。例を紹介すると、下の図の上の表は生データのロケーション情報を扱いやすい地名に変換しています。下の表は下のデータでGPSから取得した位置情報を表しています。上の表のデータは一見扱いやすいように見えますが、GPSの位置情報より情報が薄まってしまいます。なので、取得したデータは必ず生の情報として保存するようにします。

image

データは常にimmutable

データベースに書き込まれたデータは更新しない。

データのアグリゲーション

embulkなどを使う。また、非同期で並列でアップデートする。

単一障害点

分散データベースを使用する。ネットワークをパーティショニングする。

image

モデルのパブリッシュ

考慮すること

  • 学習済みモデルは保存するようにする
  • モデルの予測部分をマイクロサービス化して冗長化させる
  • マイクロサービスにはコンテナが便利。Dockerなどを使ってコンテナ化する

レスポンス

考慮すること

  • マイクロサービス化してモデルを構築する
  • スーパーバイザーを用意して各マイクロサービスを監視できるようにしておく

image

Application Serving PlatformはContainer orchestration platformと同じ意味。Kubernetesなど。
Application Serving Platformに複数のモデルサービスを持たせて分散させる。

Model Supervisorはモデルサービスの管理とモデルサービスのトラフィックの制御をする。

ProxyはNginxやHAProxyなど。

Model supervisorとProxyは基本的に同じApplication Serving Platformで動作する。

デプロイ

リリースタイミングの話ですが、一応。

image

image

ビルドが通るとArtifact Repositoryにモデルが保存されて
Serving PlatformのAPIを叩いく。Serving PlatformがArtifact Repositoryからモデルを取得してモデルをアプリケーションにデプロイする

image

Unit testとメトリクスの評価をパスしたらデプロイする。
メトリクスとはアプリケーションのパフォーマンスの指標。

Docker daemon cannot start on Ubuntu 16.04 Linux kernel 4.4

My Docker version is Docker CE 18.06.1.

$ sudo apt list --installed | grep -i docker
docker-ce/xenial,now 18.06.1~ce~3-0~ubuntu amd64 [installed]

Start dockerd.

$ sudo systemctl start docker

I got an error in syslog and cannnot start dockerd.

systemd[1]: Failed to start Docker Application Container Engine.

The cause of this error is that dockerd uses aufs storage engine in systemd startup script. Ubunt 16.04 don’t have aufs module by default. You can install aufs module but if you use Linux kernel 4.0 >=, you can use overlay2. So you can change the storage driver to --storage-driver overlay2 from --storage-driver aufs.

/etc/systemd/system/docker.service.d/10-machine.conf

[Service]
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=openstack

Start dockerd.

$ sudo systemctl start docker

さくらのレンタルサーバのPHPモジュールモードとCGIモードのレンダリング速度を比較

TL;DR

  • リプレイスされ新しくなったさくらのレンタルサーバに移行した
  • 新しいさくらのレンタルサーバと、古いさくらのレンタルサーバを使ってWordPressのレンダリング速度を比較した
  • 新しいさくらのレンタルサーバは古いさくらのレンタルサーバに比べて約2.1倍レンダリング速度が速かった

このブログはWordPressを使用して、さくらのレンタルサーバでホスティングしています。今年の4月ぐらいから、さくらのレンタルサーバのマシンが順次リプレイスされ始めていました。新しいマシンではPHPがモジュールモードになったり、ストレージがSSDになったり、WebサーバーがNginxになりHTTP/2が使えるようになりました。4月からさくらのレンタルサーバを申し込んだユーザーは新しいマシンを使えるようですが、私はリプレイス前に申し込んでいたため古いマシンでした。古いマシンのユーザーには順次新しいマシンに移行するための初期費用無料と1ヶ月の利用が無料になるクーポンを配るということでした。私にも6月ぐらいにクーポンが届いたので、どれぐらいレンダリング速度が改善するか気になっていたため8月はじめぐらいに移行してみました。移行したのはいいのですが、肝心の速度の改善について計測できていなかったので確認してみました。

計測環境

新しい環境と古い環境以下の条件を揃えています。

  • WordPressのプラグイン
  • ブログポストの内容

新しいさくらのレンタルサーバ

PHP: PHP 7.2.8 モジュールモード
Web server: Nginx (HTTP/2)

古いさくらのレンタルサーバ

PHP: PHP 7.2.8 CGIモード
Web server: Apache (HTTP/1.1)

使用したWordPressのプラグイン

  • Akismet Anti-Spam
  • Google Analytics for WordPress by MonsterInsights
  • JP Markdown
  • MathJax-LaTeX
  • WP Code Highlight.js

ブログポストの内容

約3000文字のランダムな文字列

計測に使用したアプリ

Chrome (68.0.3440.106)のAudits。
設定は以下の通りです。
Device: Desktop
Throttling: No throttling
Clear storage: True

計測方法

計測のために用意したブログポストをAuditsで計測しました。計測結果のFirst Meaningful Paintの時間を5回計測した平均を求めました。First Meaningful Paintは最初のコンテンツが表示されたとユーザーが感じる時間を表しています。

測定結果

First Meaningful Paintの値は以下の通りです。
新しいさくらのレンタルサーバ: 488ms
古いさくらのレンタルサーバ: 1026ms

古いさくらのレンタルサーバに比べて新しいさくらのレンタルサーバは約2.1倍レンダリングが早い結果となりました。体感でもキャッシュが無い状態からページを表示した時にテキストが表示される時の時間差を感じました。500ms近くも違えば早いと感じますね。

当たり前ではありますが、プラグインやコンテンツの内容によってレンダリング時間も変わります。また、共用サーバーのため他のユーザーの利用状況でも結果は変わるでしょう。ですが、新しいさくらのレンタルサーバを使った方がコンテンツのレンダリング速度の改善を見込めるので、積極的に移行すべきかと思います。

Rotation and shear mapping of Linear algebra

I created simple examples of the rotation and shear mapping of linear algebra for understanding.

My examples this repository.

This visualized example was very helpful for understanding eigenvectors and eigenvalues.

Rotation

The rotation uses below rotation matrix.
\[
A = \begin{bmatrix}
\cos\theta & -\sin\theta \\
\sin\theta & \cos\theta
\end{bmatrix}
\]

A rotated vector is represented like this;

\[
rotatedVector =
\begin{bmatrix}
\cos\theta & -\sin\theta \\
\sin\theta & \cos\theta
\end{bmatrix} v
\]

E.g.

\[
\begin{bmatrix}
\cos90 & -\sin90 \\
\sin90 & \cos90
\end{bmatrix}
\begin{bmatrix}
1 \\
1
\end{bmatrix}
=
\begin{bmatrix}
0 & -1 \\
1 & 0
\end{bmatrix}
\begin{bmatrix}
1 \\
1
\end{bmatrix}
=
\begin{bmatrix}
-1 \\
1
\end{bmatrix}
\]

Example

This is my example code.

Origin image here.

original image

Rotate it 90 degrees.

ratated

Shear mapping

The shear mapping uses below matrix.
\[
A = \begin{bmatrix}
1 & 1 \\
0 & 1
\end{bmatrix}
\]

A sheer mapping vector is represented like this;

\[
shearedVector =
\begin{bmatrix}
1 & 1 \\
0 & 1
\end{bmatrix} v
\]

E.g.

\[
\begin{bmatrix}
1 & 1 \\
0 & 1
\end{bmatrix}
\begin{bmatrix}
1 \\
1
\end{bmatrix}
=
\begin{bmatrix}
2 \\
1
\end{bmatrix}
\]

The x-axis of the vecter is increased, but the y-axis is not changed. Any other vectors are the same behaviou​r​, only the x-a​xis values are changed.

Example

This is my example code.

Sheard image.

sheard image

Traveling Germany and U.K.

I traveled to Germany and U.K. from Jul 5 to Jul 27.

My things

  • Two wallets (the one is sub)
  • Clothes (three pairs of under ware, a pair of pants, a pair of short pants, a pair of short pants for sleeping)
  • A multiple power plug adapter
  • A hand towel and face towel
  • Documents (booking information for flight, insurance)
  • A beach sandal
  • Wet Tissue
  • Teeth brash
  • A shoulder bag
  • An umbrella
  • Some of ziplock
  • Foreign language book
  • A windbreaker
  • A portable battery and some cables
  • A three-pronged outlet
  • A string for wallet
  • A camera
  • An iPhone
  • An earphone

These are the things I brought for my travel. I stuffed these things into my backpack.

Day 1 Berlin

I went to Berlin from Haneda airport via Munich.

First, arrived in Berlin central station and a hostel in Berlin. I felt sleepy by jet lag.

Post from RICOH THETA. – Spherical Image – RICOH THETA

Day 2 Berlin

I visited Berlin first.

The Berlin Wall

Post from RICOH THETA. – Spherical Image – RICOH THETA

Berlin Cathedral

Post from RICOH THETA. – Spherical Image – RICOH THETA

I tried a Berliner Weisse (Berlin style beer). I felt its taste was sour and not the beer for me, and also I didn’t have all of them because I’m full at the time.

Day 3 Berlin to Frankfurt

I moved to Frankfurt by train.
First, I missed a train and second I take on a train with an additional cost of about 140 Euro😭.

Day 4 Frankfurt

It was pretty dirty around the central station in Frankfurt. I had a Schnitzel and an Apple wine and Beer for lunch. The apple wine was good. I would like to drink it in Japan. After lunch, I took on a hop-on-hop-off bus for touring around the center of the city.

Post from RICOH THETA. – Spherical Image – RICOH THETA

Day 5 Frankfurt to Stuttgart

Post from RICOH THETA. – Spherical Image – RICOH THETA

Day 6 Stuttgart to Konstanz

I only visited the Mercedes-Benz museum. There are many cars there.

I moved by train and stay in a Switzerland side hotel.

Day 7 Konstanz

Reichenau Island

Post from RICOH THETA. – Spherical Image – RICOH THETA

Day 8 Konstanz to Garmisch-Partenkirchen

I used Flixbus’s bus to move to Garmisch-Partenkirchen because it was cheaper than the DB train. But the coach had some trouble (delay, the bus stop is far from Garmisch-Partenkirchen station about 2km), so I might I don’t use Flixbus next time.

Post from RICOH THETA. – Spherical Image – RICOH THETA

Day 9 Garmisch-Partenkirchen

I visited mount Zugspitze and AlpspiX. The town is best I visited in Germany.

Post from RICOH THETA. – Spherical Image – RICOH THETA

Day 10 Garmisch-Partenkirchen to Fussen

I went to the street the woman in the hotel telling me it’s beautiful. There are wall paintings both sides in the street 200 meters long.

Day 11 Fussen

I went to Neuschwanstein Castle. I think the castle is better in winter than in summer because the white castle’s wall matches snow.

Day 12 Fussen to Munich

Day 13 Munich

Marienplatz and around here. Notably, the new town hall and the Hofbräuhaus am Platzl was impressive.

Post from RICOH THETA. – Spherical Image – RICOH THETA

Day 14 Munich to London, U.K.

I used the easyJet to move to London from Munich. I assumed that the airline strict with hand baggage, but I was not any checks when I started boarding.

Day 15 London

I went to the British Museum in the morning. Then I met my friend then went to 10 downing street, Buckingham Palace in the afternoon, and Japan Center. I had sushi in the Japan center it is not good and more expensive than having it in Japan.

Day 16 London

I went to several markets all the day. Spitalfields Market, Covent Garden, and Borough Market. I bought a bracelet for my mother, and I could eat jellied eels in Borough Market (this was a TODO in London!😀)

Day 17 London

I went on a day trip to southern the Seven Sisters. It is a beautiful park in U.K.

Day 18 London to Preston

I met my friend and was taken around to London. The changing of the guard) in Buckingham Palace, The Tower Bridge (also we saw the opening of the gate. That is very rarely!), A brewery and tasted some beers. Then we move to Preston.

Day 19 Preston

The Morning Tea

My friend took me to Blackpool and Blackpool Tower. The tower was build based on the Eiffel Tower, and I brought some Blackpool Rock. I have a fish and chips with Mushy Peas for lunch. This is my only food in U.K. it is not good because it was very oily😅. Then we went to authentic British pubs in Preston, and I stayed at my friend’s home.

Post from RICOH THETA. – Spherical Image – RICOH THETA

Day 20 Preston to Windermere

I had a Full English Breakfast and moved to Windermere. I arrived at Windermere station evening, and I was pretty tired, so I decided just stayed a hotel in the day.

Day 21 Windermere

Orest Head is near Windermere station. It takes 15 minutes from the station on foot.

I went to Keswick by bus number 555 for seeing Castlerigg Stone Circle.

Then went Grasmere and bought some Sarah Nelson’s Grasmere Gingerbreads and then hiked from Grasmere to Rydal Mount. It takes about two hours.

Post from RICOH THETA. – Spherical Image – RICOH THETA

Then move to Waterhead pier from Rydal Mount and took on a ferry to Bowness.

When I arrived in Bowness just went to Windermere station and
a supermarket for dinner and went back to my hotel.

Day 22 Windermere to Heathrow airport

I just moved to Heathrow airport. I was in a hurry because a train I reserved from Oxenholme Lake District to London delayed about 45 minutes. I had never seen that situation. I was glad I had enough time to depart about three hours.

Post from RICOH THETA. – Spherical Image – RICOH THETA

Day 23 Tokyo, Japan

It was hot and with a high degree of moisture💦.

The unuseful things I brought for my traveling

iPad

Basically, the things we can do on iPhone and iPad are same. The iPad just has a bigger display. I thought it was useful for reading books, but I could read with my iPhone, not iPad. And also I didn’t have time to use iPad in hotels or some other places. It only gained my baggage weight.

A pair of pants

Germany and U.K. were hot! I don’t need it in July.

Foreign language textbook

I couldn’t speak and listen German just by learning German for a few days.

A three-pronged outlet

This outlet was for only Japan. I didn’t notice that. For instance, it didn’t work on 240v.

A string for wallet

It was just annoying. Eventually, I didn’t use it.

About three weeks traveling

I enjoyed at first of one week. But after two weeks, I was starting to be boring. So I think that the number of days for traveling is about 10 days for me.

Full-text search for Japanese with ngram full-text parser

TL;DR

Create an index with ngram full-text parser.

CREATE FULLTEXT INDEX idx_message_log ON message_log (message) WITH PARSER ngram;

ngram Full-Text Parser

We want to do a full-text search for searching our entire texts that exist about 150k rows. Its text is written in Japanese.

Our table like this;

create table message_log
(
    id int(11) unsigned not null,
    message varchar(255) default '' not null,
    primary key (id)
);

If message column would fill out in English or other space-separated languages, you can create a full-text index.

CREATE FULLTEXT INDEX idx_message_log on message_log.message (message);

However, we treat the message as Japanese. In this case, we cannot get any message, because Japanese is not the text space-separated words.

For instance, Japanese like this;

好きなメンバーとその理由を教えて下さい!

not space-separated;

好きな メンバー と その 理由 を 教えて 下さい!

We assume we have this record.

id message
1 好きなメンバーとその理由を教えて下さい!
2 好きな メンバー と その 理由 を 教えて 下さい!

We find messages with the full-text search function.

SELECT * FROM message_log WHERE MATCH (message) AGAINST ('メンバー');

And then get this result.

id message
2 好きな メンバー と その 理由 を 教えて 下さい!

We expect to can get all records. However, it does not include the text 好きなメンバーとその理由を教えて下さい!.

So we create an index with ngram full-text parser.

CREATE FULLTEXT INDEX idx_message_log ON message_log (message) WITH PARSER ngram;

Again, find messages.

SELECT * FROM message_log WHERE MATCH (message) AGAINST ('メンバー');

And then we get ID 1 and 2 that we expected.

id message
1 好きなメンバーとその理由を教えて下さい!
2 好きな メンバー と その 理由 を 教えて 下さい!

Building a CI for Golang test

I built a CI with Jenkins for Golang test. We run go test on a Docker container and even run Jenkins on a Docker container.

Directories

app
├── docker
│   ├── dockerfiles # Dockerfiles for unit test
│   └── test
│       ├── init-db.sh # This initializes DB before testing
│       └── test.sh # Testing script
└── Jenkinsfile # The configuration for Jenkins pipeline

Environment of CI

Our Jenkins server uses an EC2 instance of t2.large, and the server runs on Docker container, and even a unit test run on Docker container on the container Jenkins runs with /var/run/docker.sock.

Jenkins loads Jenkinsfile and then execute it on the Jenkins pipeline.

How to build an execution environment

Create an AWS EC2 instance

We prepare the instance of EC2 installed Docker CE. Please see Get Docker CE for CentOS installation guide.

Create a Docker image for golang unit test

Jenkins

Dockerfile

FROM jenkins/jenkins:lts

# Switch to root user
USER root

# Install Docker
RUN apt-get update
RUN apt-get install -y \
     apt-transport-https \
     ca-certificates \
     curl \
     gnupg2 \
     software-properties-common

RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
RUN add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"
RUN apt-get update
RUN apt-get install -y docker-ce
RUN echo "jenkins ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# Switch back to jenkins user
USER jenkins

# Set system timezone JST

ENV TZ Asia/Tokyo

Run on a Jenkins host.

$ docker build --rm --tag jenkins-docker:latest .

Golang

Dockerfile

FROM circleci/golang:1.9

# Install goose
RUN curl https://glide.sh/get | sh
RUN go get bitbucket.org/liamstask/goose/cmd/goose

# Set system timezone JST
ENV TZ Asia/Tokyo

Run on our Jenkins host.

$ docker build --rm --tag golang:latest .

Launch Jenkins

$ sudo docker run --env JAVA_OPTS=-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Tokyo -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -d -p 80:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins-docker:latest

-v /var/run/docker.sock:/var/run/docker.sock is used to manipulate host’s Docker because we want to launch containers on host-side.

-v jenkins_home:/var/jenkins_home is used to store everything of our Jenkins configurations and build results on our host’s filesystem. If you
move your Jenkins to another host or backup your Jenkins data, read this page.

Add a job to Jenkins

We need to enable Jenkins to hook Pull Requests when some developer does it.

Below settings on Jenkins.

Add a credential of GitHub enterprise

Because we use GitHub enterprise our development.

Credentials -> Jenkins -> Global credentials -> Add Credentials

key value
Kind Username with password
Scope Global
Username ci
Password ****

Add our GitHub enterprise

Configure System -> GitHub Enterprise Servers

key value
API endpoint http://***/api/v3
Name GitHub Enterprise

Create a job

New Item -> GitHub Organization -> OK

Configure the job’s settings

<Job> -> Configure -> Projects

key value
API endpoint GitHub Enterprise (http://***/api/v3)
Credentials ci/****
Owner some-repogitory
Script Path Jenkinsfile

<Job> -> Configure -> Projects -> Behaviours

key value
Filter by name (with regular expression) some-repogitory
Discover pull request from forks – Strategy Merging the pull request with the current target branch revision
Discover pull request from forks – Trust Everyone

Create a webhook in GitHub

To hook PR in Jenkins, We need to create a webhook in GitHub. Note that we must use the user has right permission.

<your repository> -> Settings -> Hooks

key value
Payload URL http://***/github-webhook/
Content type application/json
Which events would you like to trigger this webhook? Send me everything
Active true

Disable Jenkins’ authentication

Because we use Jenkins in a secure place, there are no incoming packets from the internet.

Manage Jenkins -> Configure Global Security -> Access Control -> Authorization -> check Anyone can do anything

Upgrade Jenkins

Since jenkins_home Docker volume has all Jenkins’ setting files, We pull a latest Docker image and relaunch Docker container, that’s it!

$ sudo docker stop jenkins
$ sudo docker rm jenkins
$ sudo docker pull jenkins/jenkins:lts
$ cd app/docker/dockerfiles/jenkins
$ docker build --rm --tag jenkins-docker:latest .
$ sudo docker run --env JAVA_OPTS=-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Tokyo -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -d -p 80:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins-docker:latest

Jenkinsfile template

The Jenkinsfile we use, almost same, like this;

pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                step($class: 'GitHubSetCommitStatusBuilder')
                checkout scm
            }
        }

        stage('Start up containers') {
            steps {
                sh "sudo docker network create ci${env.EXECUTOR_NUMBER}"

                sh "sudo docker run -d --name mysql${env.EXECUTOR_NUMBER} --network ci${env.EXECUTOR_NUMBER} -p 3306${env.EXECUTOR_NUMBER}:3306 circleci/mysql:5.7"
                sleep(10)
                sh "sudo docker run -d --name redis${env.EXECUTOR_NUMBER} --network ci${env.EXECUTOR_NUMBER} redis:4.0"

                script {
                    if (sh (
                            script: "sudo docker create --name golang${env.EXECUTOR_NUMBER} --network ci${env.EXECUTOR_NUMBER} golang:latest bash /go/src/app/docker/test/test.sh",
                            returnStatus: true
                    ) == 0) {
                        sh "sudo docker cp ${env.WORKSPACE} golang${env.EXECUTOR_NUMBER}:/go/src/leo-server"
                    }
                }
            }
        }

        stage('Initialize containers') {
            steps {
                // Initialize something like DB
            }
        }

        stage('Unit test') {
            steps {

                script {
                    if (sh (
                            script: "sudo docker start -a golang${env.EXECUTOR_NUMBER}",
                            returnStatus: true
                    ) != 0) {
                        currentBuild.result = 'FAILURE'
                    }
                }

                // Copy test report and convert it into junit xml report
                sh "sudo docker cp golang${env.EXECUTOR_NUMBER}:/go/src/app/report.xml ."

                step([$class: 'JUnitResultArchiver', testResults: 'report.xml'])
            }
        }
    }

    post {
        always {
            sh script: "sudo docker stop mysql${env.EXECUTOR_NUMBER}", returnStatus: true
            sh script: "sudo docker stop redis${env.EXECUTOR_NUMBER}", returnStatus: true

            sh script: "sudo docker rm mysql${env.EXECUTOR_NUMBER}", returnStatus: true
            sh script: "sudo docker rm redis${env.EXECUTOR_NUMBER}", returnStatus: true
            sh script: "sudo docker rm golang${env.EXECUTOR_NUMBER}", returnStatus: true

            sh script: "sudo docker network rm ci${env.EXECUTOR_NUMBER}", returnStatus: true
        }
    }
}

Our test script, test.sh, like this;

#!/bin/bash

sudo chown -R circleci:circleci /go/src

cd /go/src/leo-server

echo 'Installing go-packages...'
glide i

echo 'Migrating DBs...'
go get bitbucket.org/liamstask/goose/cmd/goose
goose -env=ci -path=database/user up

echo 'Installing testing libraries...'
go get -u github.com/jstemmer/go-junit-report

echo 'Testing...'
go test -v ./... 2>&1 > tmp
status=$?
go-junit-report < tmp > report.xml

exit ${status}

Golangでユニットテスト書くテクニック

Goは他のフレームワークにあるような大きなアサーションツールを持っていません。Goでは testing.T オブジェクトのメソッドがテストに使われます。

  • T.Error(args ...interface{}) または T.Error(msg string, args interface{}) はメッセージを受け取ってテストを失敗させるために使用されます
  • T.Fatal(args ...interface{}) または T.Fatal(mst string, args interface{})T.Error() と似ていますがテストが失敗すると、それ以降のテストは実行されません。テストが失敗した時それ以降のテストも失敗する場合、 T.Fatal() を使うべきです

以下ではGoのテスト使用される2つのテクニックを紹介します。

モックとスタブにインターフェースを使用する

外部ライブラリに依存したコードを書いていて、その外部ライブラリが正しく利用されているかテストしたいときを考えます。

Goのインターフェースはメソッドの期待する動作を表しています。 例として io.Writer を見てみます。

type Writer interface {
    Write(p []byte) (n int, err error)
}

io.Writer インターフェースは引数で受け取ったバイト列を書き込みますが、このインターフェースは os.Fileなどで実装されています。Goのtypeシステムではどのインターフェースを使うか明示する必要がありません。既存のtypeのプロパティと一致するインターフェースを宣言することで、外部ライブラリの動作を変更することができます。

例を見ていきましょう。

以下のようなメッセージを送信する外部ライブラリがあります。

type Message struct {
     // ...
}

func (m *Message) Send(email, subject string, body []byte) error {
     // ...
     return nil
}

これをそのまま使うのではなくMessageを使うMessagerインターフェースを作成します。

type Messager interface {
    Send(email, subject string, body []byte) error
}

Alertメソッドでメッセージを送信することを考えます。Message typeを直接渡すのではなくMessager引数で受け取って、インターフェースのSendメソッドを呼び出すようにします。

func Alert(m Messager, problem []byte) error {
    return m.Send("example@example.com", "Critical Error", problem)
}

このようにMessageを抽象化したmessagerを使うことで簡単にモックを作成してテストすることができます。

具体的には以下のようになります。

package msg

import (
    "testing"
)

type MockMessage struct {
    email, subject  string
    body            []byte
}

func (m *MockMessage) Send(email, subject string, body []byte) error {
    m.email = email
    m.subject = subject
    m.body = body
    return nil
}

func TestAleart(t *testing.T) {
    msgr := new(MockMessage) // モックのメッセージを作成します
    body := []byte("Critical Error")

    Alert(msgr, body) // Aleartメソッドを実行します

    if msgr.subject != "Critical Error" {
        t.Errorf("Expected 'critical Error', Got '%s'", msgr.subject)
    }
}

Messagerインターフェースを実装するためにMockMessage typeを作成します。MockeMessageではMessagerと同じSend()が実装されています。このSend()はメーセージを実際に送信するのではなくデータをオブジェクトに保存しておくことでテストしやすくなります。

また、このようにインターフェースを使った抽象化をすることで、後にSend()の動作を変えなければいけなくなった時に簡単に変えられるようになります。

カナリアテスト

外部ライブラリを使っているとメジャーバージョンアップの時などにメソッドの引数が変わることがあります。

例えば、io.Writerを新しく実装していたとします。これをライブラリとして公開していて、他のコードがこれを使用しています。以下のようなコードです。

type MyWriter struct{
     // ...
}

func (m *MyWriter) Write([]byte) error {
     // どこかにデータを書き出す
     return nil
}

ぱっと見io.Writeを実装しているように見えますが、正しくはWrite(p []byte) (n int, err error)です。なのでio.Writeを実装できていません。

次に、type assertionを使ってコードを書いてみます。

func main() {
    m := map[string]interface{}{
        "w": &MyWriter(),
    }
}

func doSomething(m map[string]interface{}) {
    w := m["w"].(io.Writer) // runtime exceptionになる
}

このコードはコンパイルとは通りますが、runtimeでexceptionになります。

これを防ぐために以下のようなカナリアテストを追加します。(ちなみにカナリアテストは”canary in the coal mine”から来ているようです)

func TestWriter(t *testing.T) {
    var _ io.Writer = &MyWriter{} // コンパイラにtype assertionをやってもらう
}

このテストはもちろん失敗します。このようにtype assertionを使ってテストすることで、インターフェースを正しく実装できているか確認することができます。また、外部ライブラリのシグネチャの変更にも気づくことができます。