Linkerd入門-その1-概要とカナリアリリース

# はじめに
Kubernetesを利用している人の間では、最近istioが話題になりはじめたようですが、[Linkerd](https://linkerd.io/)については、[istio](https://istio.io/)と同様の機能を持ち商用サービスで多くの実績もあるにもかかわらず、話が出る事は少ないように思えます。この記事では、Linkerdをご紹介し、サービスメッシュの機能の1つであるカナリアリリースを試します。

# Linkerdとは
Linkerdは、クラウドネイティブアプリケーションの為のOSSのサービスメッシュです。[Cloud Native Computing Foundation](https://www.cncf.io/)(以下「CNCF」と言います)の一員であり、同じようなCNCFのサービスメッシュにはistioのデータプレーンであるEnvoyがあります。
また、Linkerdを開発したBuoyant社がLinkerdの教訓を元にしたKubernetes専用のサービスメッシュConduitを2017年12月に発表しています。ConduitはLinkerdの後継ではなく、Linkerdの開発とサポートは継続すると[発表](https://buoyant.io/2017/12/05/introducing-conduit/)されています。（が、Conduitの実装が進めばLinkerdは使われなくなるでしょうね。）

サービスメッシュの必要性は、BuoyantのCEOであるWilliamのpost“[What is a service mesh? And why do I need one?](https://buoyant.io/2017/04/25/whats-a-service-mesh-and-why-do-i-need-one/)”に書かれているように、複雑化したマイクロサービスの課題を解決する事にあります。

Linkerdは、各サービスの間にProxyとして配置される事で、マイクロサービスの問題を解決するものです。

# Linkerdを選ぶ理由
バージョン番号で比較できるものではありませんが、2017年12月現在でもistioは1.0に達してませんし、Conduitは発表されたばかりです。今Linkerdを選ぶ理由があるとすると、金融系を含む商用サービスでの多くの実績になるでしょう。


#システム構成
サービスメッシュの配置には、node毎に配置するper nodeと、pod毎に配置するper pod（サイドカーとも呼ばれる）があります。Linkerdは、per nodeもper podも対応できますが、per node構成が推奨されています。per node構成では、LinkerdはKubernetesのdaemonsetとして配置されます。多数の軽量のpodを配置しても、per node配置ではLinkerdが増える事はありませんので、リソース削減の観点ではper podよりも有利です。

![perpod.jpg](https://qiita-image-store.s3.amazonaws.com/0/158632/1838c30a-2b06-db0b-dab5-19bf49f21641.jpeg)

上記の図では、pod4にあるサービスからpod8にあるサービスへリクエストを送る様子を描いています。pod4のサービスは同一ノードnode1にあるLinkerdにリクエストを送ります。node1のLinkerdはdtabルールに従いnode2のLinkerdにリクエストを送り、node2のLinkerdはpod8のサービスに送ります。


## Linkerdとnamerd
先ほどから図に出てくるnamerdについて説明します。
namerdは[dtabs(Delegation tablesの略)](https://linkerd.io/advanced/dtabs/)によるルーティング情報を中央管理し、Linkerdからルーティング情報を参照されるものです。
Linkerd単独利用も可能ですが、dtabの変更に再デプロイが必要であるため、カナリアリリースやred/blackデプロイメントをしたい場合にはnamerdが必要となります。
なお、カナリアリリースとは、新しいバージョン等をリリースする際にまず5%のトラフィックを新バージョンに流し、問題がないと確信を持った後に100%のトラフィックを新バージョンに流すといったリリース方法です。red/blackデプロイメントは、新しいバージョンを用意し一気に切り替えるリリース方法です。

# サンプルアプリケーションの概要
[linkerd-example](https://github.com/linkerd/linkerd-examples/tree/master/k8s-daemonset/k8s)を使って、カナリアリリースを試します。
まず、Linkerdとnamerdを配置したあと、[helloworldアプリケーション](https://github.com/linkerd/linkerd-examples/tree/master/docker/helloworld)を配置します。このhelloworldアプリケーションは、helloサービスがリクエストを受け取り、helloサービスはworldサービスを呼び出してからレスポンスを返すものです。helloサービスは「hello」という文字列を返し、worldサービスは「world」という文字列を返す簡単なサービスです。

worldサービスはworld-v1サービスとworld-v2サービスの2種類を用意し、最初はhelloサービスからworld-v1サービスに全トラフィックをルーティングするように設定します。
次にhelloサービスからの１割のトラフィックをworld-v2サービスにルーティングし、最後にhelloサービスからの全トラフィックをworld-v2サービスにルーティングしていく様子を見ていきます。

#サンプルアプリケーションの配置
###namerctlコマンドラインツールのインストール
namerdの操作ではnamerctlコマンドラインツールを使いますので、あらかじめインストールします。

```Bash:
$ go get github.com/linkerd/namerctl
$ go install github.com/linkerd/namerctl
```

###GKEクラスタの作成
今回はKubernetesのマネージメントサービスであるGKEを使って、Kubernetesクラスタを作成します。
ここでは、クラスタ名をsample-l5dとし、ノード数は2ノードから5ノードまでのオートスケールとします。

```Bash:
$ gcloud container clusters create --enable-autoscaling --min-nodes=2 --max-nodes=5 sample-l5d
Creating cluster sample-l5d...done.
Created [https://container.googleapis.com/v1/projects/ecl2-logging-lab1ec/zones/us-central1-c/clusters/sample-l5d].
kubeconfig entry generated for sample-l5d.
NAME        ZONE           MASTER_VERSION  MASTER_IP      MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
sample-l5d  us-central1-c  1.7.8-gke.0     35.188.122.84  n1-standard-1  1.7.8-gke.0   3          RUNNING


Updates are available for some Cloud SDK components.  To install them,
please run:
  $ gcloud components update

$ gcloud container clusters get-credentials sample-l5d
Fetching cluster endpoint and auth data.
kubeconfig entry generated for sample-l5d.

```

###サンプルのダウンロード
gitからサンプルをcloneしてきます。

```Bash:
$ git clone https://github.com/linkerd/linkerd-examples.git
```

###namerdの配置
namerdを配置します。
この際にJobによりnamerctlコマンドラインが実行され、internalとexternalの2つのnamespaceにdtabルールが登録されます。
Linkerdとの通信には4100番ポート、namerctlとの通信には4180番ポートを使います。

```Bash:
$ cd linkerd-examples/
$ kubectl apply -f k8s-daemonset/k8s/certificates.yml
secret "certificates" created
$ kubectl apply -f k8s-daemonset/k8s/namerd.yml
thirdpartyresource "d-tab.l5d.io" created
configmap "namerd-config" created
replicationcontroller "namerd" created
service "namerd" created
configmap "namerctl-script" created
job "namerctl" created
$ kubectl get all
NAME            DESIRED   SUCCESSFUL   AGE
jobs/namerctl   1         0            1m

NAME                READY     STATUS    RESTARTS   AGE
po/namerctl-xq7qq   1/1       Running   0          1m
po/namerd-f7k8t     2/2       Running   0          1m

NAME        DESIRED   CURRENT   READY     AGE
rc/namerd   1         1         1         1m
```

###dtabルールの確認
登録されたdtabルールをnamerctlコマンドで確認してみると、internalとexternalのルールが書かれている事が分かります。internalのdtabルールには、worldにリクエストするとworld-v1サービスへルーティングされるように書かれています。

```Bash:
$ NAMERD_INGRESS_LB=$(kubectl get svc namerd -o jsonpath="{.status.loadBalancer.ingress[0].*}")
$ export NAMERCTL_BASE_URL=http://$NAMERD_INGRESS_LB:4180
$ namerctl dtab get internal
# version ODk5
/srv         => /#/io.l5d.k8s/default/http ;
/host        => /srv ;
/tmp         => /srv ;
/svc         => /host ;
/host/world  => /srv/world-v1 ;
$ namerctl dtab get external
# version ODk4
/host   => /#/io.l5d.k8s/default/http/hello ;
/svc/*  => /host ;
```

###dtabルールの読み方
dtabは、論理パスから具体名に変換するものです。
例えば、helloサービスからworldへhttpリクエストが送られた場合、/svc/worldという論理パスが割り当てられ、以下のようにステップバイステップで変換されます。最終的に/#/io.l5d.k8s/default/http/world-v1という具体名に変換されますが、これはk8sのdefault namespaceのhttpのworld-v1サービスのLinkerd LoadBalancerを示しています。


| 変換前               |適用されるdtabルール           | 変換後    |
|:-------------------|:------------------------------|:------------------|
| /svc/world         | /svc  => /host ; | /host/world     |
| /host/world         | /host/world  => /srv/world-v1 ; | /srv/world-v1    |
| /srv/world-v1       | /srv  => /#/io.l5d.k8s/default/http ;|/#/io.l5d.k8s/default/http/world-v1  |

dtabによる変換の様子はLinkerdの配置後、http://$L5D_INGRESS_LB:9990/delegatorで確認できますのでお試しください。

### namerdのダッシュボードでdtabルールを確認
namerdにはダッシュボードも用意されています。
ここでも、externalとinternalのdtabルールが登録されていることを確認できます。

```Bash:
$ open http://$NAMERD_INGRESS_LB:9991 # only mac
```

<img width="678" alt="スクリーンショット 2017-12-16 12.22.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/158632/efa005e9-5859-e167-dadd-42466dcbb6f4.png">

<img width="676" alt="スクリーンショット 2017-12-16 12.23.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/158632/d08a4235-d550-642c-398e-54fccffe6529.png">

external namespace に登録されているDtabが確認できます。外部からの全てのリクエストをhelloサービスにルーティングする設定です。

<img width="672" alt="スクリーンショット 2017-12-16 12.23.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/158632/858afa36-3ea7-cb5a-d41d-199b05c934fc.png">

internal namespaceに登録されているDtabが確認できます。helloからworldにルーティングする設定です。



###Linkerdの配置
Linderdはper nodeであるDaemonSetとして配置され、namerdを使うように設定されます。

```Bash:
$ kubectl apply -f k8s-daemonset/k8s/linkerd-namerd.yml
configmap "l5d-config" created
daemonset "l5d" created
service "l5d" created
$ L5D_INGRESS_LB=$(kubectl get svc l5d -o jsonpath="{.status.loadBalancer.ingress[0].*}")
$ open http://$L5D_INGRESS_LB:9990 # only mac
```

### Linkerdのダッシュボード
Linkerdのダッシュボードは9990ポートで確認できます。

```Bash:
$ open http://$L5D_INGRESS_LB:9990 # only mac
```

<img width="1276" alt="スクリーンショット 2017-12-16 13.45.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/158632/c4afad9b-81ce-df84-b9dd-c0555c2f6033.png">



### helloサービスとworldサービスの配置
サンプルアプリケーションとして、helloとworld-v1の2つのサービスを配置します。
以下のようにhelloは、worldを呼びます。
なお、helloとworldで実行されるバイナリは同じものですが、引数により振る舞いを変えています。

hello -> linkerd (outgoing) -> linkerd (incoming) -> world


```Bash:
$ kubectl apply -f k8s-daemonset/k8s/hello-world.yml
replicationcontroller "hello" created
service "hello" created
replicationcontroller "world-v1" created
service "world-v1" created
```


### アプリケーションへリクエストしてみる
helloサービスから「Hello (10.4.4.7) 」が返り、helloサービスから呼び出されたworldサービスが「world (10.4.2.6)」を返しています。

```Bash:
$ curl $L5D_INGRESS_LB
Hello (10.4.4.7) world (10.4.2.6)!!
```

#カナリアリリースを試す

### world-v2の配置
world-v2を配置します。
world-v1からworld-v2へのトラフィック移動を確認するために、world-v1とは違って、worldではなくearthと答えるように引数の設定を変えたものになっています。

```Bash:
$ kubectl apply -f k8s-daemonset/k8s/world-v2.yml
replicationcontroller "world-v2" created
service "world-v2" created
```

###全てのリクエストがworld-v1に送られている事を確認する
10回ほどループしながらリクエストを送ってみます。現時点では、全てのリクエストはworld-v1に送られている事を確認できます。

```Bash:
$ for i in {1..10}; do curl $L5D_INGRESS_LB; echo ""; done
Hello (10.8.2.6) world (10.8.2.8)!!
Hello (10.8.1.9) world (10.8.0.5)!!
Hello (10.8.2.7) world (10.8.0.5)!!
Hello (10.8.1.9) world (10.8.2.8)!!
Hello (10.8.2.6) world (10.8.0.5)!!
Hello (10.8.1.9) world (10.8.2.8)!!
Hello (10.8.2.7) world (10.8.0.5)!!
Hello (10.8.1.9) world (10.8.0.5)!!
Hello (10.8.2.6) world (10.8.0.5)!!
Hello (10.8.1.9) world (10.8.2.8)!!
```


###dtab書き換えによる一部トラフィックの移動
namerdのdtabルールを書き換えて、一部のトラフィックをworld-v2にルーティングしてみます。
このルール変更では、１割のworldへのリクエストはworld-v2にルーティングし、９割はworld-v1へルーティングするものです。
例では標準入力からdtabを読み込んでいますが、標準入力の代わりにファイルを指定する事もできます。

```Bash:
$ namerctl dtab get internal
# version ODk5
/srv         => /#/io.l5d.k8s/default/http ;
/host        => /srv ;
/tmp         => /srv ;
/svc         => /host ;
/host/world  => /srv/world-v1 ;
$ namerctl dtab update internal - <<EOF
> /srv         => /#/io.l5d.k8s/default/http ;
> /host        => /srv ;
> /tmp         => /srv ;
> /svc         => /host ;
> /host/world  => 9 * /srv/world-v1  & 1 * /srv/world-v2;
> EOF
Updated internal
$ namerctl dtab get internal
# version MTI4MjQ=
/srv         => /#/io.l5d.k8s/default/http ;
/host        => /srv ;
/tmp         => /srv ;
/svc         => /host ;
/host/world  => 9.00*/srv/world-v1 & /srv/world-v2 ;
```

###トラフィック移動の確認
ループしながらcurlでリクエストを送ってみると、一部トラフィックがworld-v2からの応答であるearthという文字列として返ってきている事を確認できます。

```Bash:
$ for i in {1..10}; do curl $L5D_INGRESS_LB; echo ""; done
Hello (10.8.2.6) world (10.8.0.5)!!
Hello (10.8.1.9) world (10.8.0.5)!!
Hello (10.8.1.9) world (10.8.3.2)!!
Hello (10.8.2.6) world (10.8.3.2)!!
Hello (10.8.2.6) world (10.8.2.8)!!
Hello (10.8.2.6) world (10.8.0.5)!!
Hello (10.8.2.6) world (10.8.0.5)!!
Hello (10.8.1.9) earth (10.8.3.5)!!
Hello (10.8.1.9) world (10.8.2.8)!!
Hello (10.8.2.7) world (10.8.2.8)!!
```

###全トラフィックを切り替え
worldへの全トラフィックをworld-v2にルーティングしてみましょう。

```Bash:
$ namerctl dtab update internal - <<EOF
> /srv         => /#/io.l5d.k8s/default/http ;
> /host        => /srv ;
> /tmp         => /srv ;
> /svc         => /host ;
> /host/world  =>  /srv/world-v2;
> EOF
Updated internal
$ namerctl dtab get internal
# version MTYzNzk=
/srv         => /#/io.l5d.k8s/default/http ;
/host        => /srv ;
/tmp         => /srv ;
/svc         => /host ;
/host/world  => /srv/world-v2 ;
```


###全トラフィック移動を確認
ループしながらリクエストを送り、全てのトラフィックがworld-v2にルーティングしたことを確認できます。

```Bash:
$ for i in {1..1000}; do curl $L5D_INGRESS_LB; echo ""; done
Hello (10.8.1.9) earth (10.8.3.6)!!
Hello (10.8.1.9) earth (10.8.3.5)!!
Hello (10.8.2.6) earth (10.8.3.6)!!
Hello (10.8.1.9) earth (10.8.3.6)!!
Hello (10.8.1.9) earth (10.8.3.4)!!
Hello (10.8.2.6) earth (10.8.3.6)!!
Hello (10.8.2.6) earth (10.8.3.5)!!
Hello (10.8.2.7) earth (10.8.3.5)!!
Hello (10.8.2.6) earth (10.8.3.4)!!
Hello (10.8.2.7) earth (10.8.3.5)!!
Hello (10.8.2.7) earth (10.8.3.5)!!
Hello (10.8.1.9) earth (10.8.3.4)!!
```


###ダッシュボード確認
ダッシュボードでトラフィックを確認する事もできます。

<img width="1267" alt="スクリーンショット 2017-12-16 14.32.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/158632/e2fc1157-9a83-f0c9-a7eb-7079b646155c.png">



#おわりに
今回は、Linkerdとnamerdを用いて、重み付けによるルーティング先の移動を試しました。
まずは少しだけトラフィックを移し、その後全てのトラフィックを移すといったカナリアリリースに利用できる事が分かりました。
それでは、Linkerd入門その2をお楽しみに。