やさしい Service Mesh を求めて、Maesh でできること

[Containous](https://containo.us/) から 2019/11/21 に [Maesh](https://mae.sh/) の [v1.0.0 がリリース](https://blog.containo.us/maesh-1-0-802f4be9a9a7)されました :tada:

[Maesh](https://mae.sh/) は Service Mesh の OSS で、DaemonSet により各ノードで起動しているプロキシエンドポイントを利用してルーティングを処理します。  
そのため一部のアプリケーションに導入することや、Kubernetes の Service からの移行が簡単になっている構成になります。

<div align="center">
<img width="320px" src="https://github.com/containous/maesh/blob/master/docs/content/assets/img/before-maesh-graphic.png?raw=true"/>
<img width="320px" src="https://github.com/containous/maesh/blob/master/docs/content/assets/img/after-maesh-graphic.png?raw=true"/>
</div>

サイドカーによる Service Mesh の導入ではマニフェストの管理を動的生成と静的生成、どちらで管理するかも悩ましいですし、コンテナ間の Graceful Shutdown への対応も考慮する必要があります。  [^1]  
ただし mTLS などの要件は DaemonSet の方式では満たすことができないので注意が必要です。[^2]

動作するプラットフォームは Kubernetes のみとなっていますが、その分最適化されているのでインストールはもちろんのこと、利用方法も簡単になっています。

機能も Service Mesh として十分な機能が実装されています。

- Load balancing
    - Protocols
        - HTTP layer
            - HTTPS, HTTP/2, native gRPC, Websockets
        - TCP layer
            - Route raw TCP connection for any applicative protocol other than HTTP
- Retries & Failovers
- Circuit breakers & Rate limits
- OpenTracing Support
- Traffic Metrics

## 最小限のインストール
詳しいインストール方法は[公式ドキュメント](https://docs.mae.sh/install/)を参照してください。

ここでは Helm v3 を使い、最小限のコンポートネントからインストールを始め、それぞれの動きを紹介します。

```
# Maesh 用のネームスペースを作成
 ❯❯ kubectl create namespace maesh && kubectl config set-context $(kubectl config current-context) --namespace=maesh

# Helm のバージョンは v3 を利用
 ❯❯ helm version
version.BuildInfo{Version:"v3.0.0", GitCommit:"e29ce2a54e96cd02ccfce88bee4f58bb6e2a28b6", GitTreeState:"clean", GoVersion:"go1.13.4"}

# Maesh のリポジトリを登録
 ❯❯ helm repo add maesh https://containous.github.io/maesh/charts && helm repo update

# マニフェスト内容を確認したいので一旦ローカルディスクに保存
 ❯❯ helm pull maesh/maesh --untar && cd maesh

# ファイルの確認
 ❯❯ ls -al
.rw-r--r--  378 watawuwu 23 Nov 15:42 .helmignore
.rw-r--r--  382 watawuwu 23 Nov 15:42 Chart.yaml
drwxr-xr-x    - watawuwu 23 Nov 15:42 charts
drwxr-xr-x    - watawuwu 23 Nov 15:42 crds
.rw-r--r-- 3.2k watawuwu 23 Nov 15:42 Guidelines.md
.rw-r--r--  147 watawuwu 23 Nov 15:42 requirements.yaml
drwxr-xr-x    - watawuwu 23 Nov 15:42 templates
.rw-r--r-- 1.3k watawuwu 23 Nov 15:42 values.yaml

# 最小限のコンポーネントでインストール
# pdb や config などの設定はあるが、コンポーネントとしては maesh-mesh と maesh-controller の 2 つとなる
 ❯❯ helm template maesh . \
     --set controller.image.tag=v1.0.0 \
     --set mesh.image.tag=v2.1 \
     --set smi.deploycrds=false \
     --set smi.enable=false \
     --set tracing.deploy=false \
     --set tracing.jaeger.enabled=false \
     --set metrics.deploy=false \
     --set metrics.prometheus.enable=false \
     | kubectl apply -f-
poddisruptionbudget.policy/maesh-controller created
poddisruptionbudget.policy/maesh-mesh created
configmap/tcp-state-table created
serviceaccount/maesh-controller created
serviceaccount/maesh-mesh created
clusterrole.rbac.authorization.k8s.io/maesh-controller-role created
clusterrolebinding.rbac.authorization.k8s.io/maesh-controller created
service/maesh-mesh-api created
daemonset.apps/maesh-mesh created
deployment.apps/maesh-controller created

# 2 つのコンポーネントを確認
 ❯❯ kubectl get po
NAME                                READY   STATUS    RESTARTS   AGE
maesh-controller-6fd865fc97-lv8tm   1/1     Running   0          22s
maesh-mesh-2zlxk                    1/1     Running   0          22s
maesh-mesh-s5fg5                    1/1     Running   0          22s
```

Deployment/**maesh-controller** と DaemonSet/**maesh-mesh** の 2 つのコンポーネントが作成されました。

では次に挙動を確認していきます。

## 基本的な挙動の確認

maesh-controller は起動時に、CoreDNS の設定ファイルが登録された ConfigMap を取得します。  
もし ConfigMap のラベルに `maesh-patched: "true"` がなければ以下のような設定を追加し、`maesh` という Server Block を追加します。

```
 ❯❯ kubectl -n kube-system get cm coredns -ojson | jq -r '.data.Corefile'
...
maesh:53 {
    errors
    rewrite continue {
        name regex ([a-zA-Z0-9-_]*)\.([a-zv0-9-_]*)\.maesh maesh-{1}-{2}.maesh.svc.cluster.local
        answer name maesh-([a-zA-Z0-9-_]*)-([a-zA-Z0-9-_]*)\.maesh\.svc\.cluster\.local {1}.{2}.maesh
    }
    kubernetes cluster.local in-addr.arpa ip6.arpa {
        pods insecure
        upstream
        fallthrough in-addr.arpa ip6.arpa
    }
    forward . /etc/resolv.conf
    cache 30
    loop
    reload
    loadbalance
}
```

ConfigMap に設定が追加されたら、CoreDNS へ設定を反映するため、CoreDNS の Deployment のアノテーションにハッシュ値を追加し、Pod を再作成します。

```
 ❯❯ kubectl -n kube-system get deploy coredns -ojson | jq '.spec.template.metadata.annotations'
{
  "maesh-hash": "b128cd46-8dc9-41f2-a91b-94f9edac5f06"
}
```

これらの挙動は **CoreDNS とその構成に強く**依存しているので、自分の環境で試す際には注意が必要です。  
ただし、CoreDNS を利用していない GKE の場合は、kube-dns を利用する方法で別途対応がされています。

この設定に加え、maesh-controller は `maesh` ネームスペース以外の Service が作成されると、`maesh` ネームスペースに maesh-mesh へルーティングする Service を作成します。

```
# maesh ネームスペースには、maesh-mesh-api だけが作成されている
 ❯❯ kubectl -n maesh get svc
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
maesh-mesh-api   ClusterIP   10.107.67.206   <none>        8080/TCP   80s


# default ネームスペースに test Service を作成すると
 ❯❯ kubectl -n default create svc clusterip test --tcp=8080:8080
service/test created

# maesh-test-default(maesh-${service-name}-${namespace}) という Service が作成される
 ❯❯ kubectl -n maesh get svc
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
maesh-mesh-api       ClusterIP   10.107.67.206   <none>        8080/TCP   100s
maesh-test-default   ClusterIP   10.100.68.223   <none>        8080/TCP   4s

# selector の条件として maesh-mesh が登録される
 ❯❯ kubectl get svc maesh-test-default -oyaml
...
spec:
  clusterIP: 10.100.68.223
  ports:
  - name: 8080-8080
    port: 8080
    protocol: TCP
    targetPort: 5000
  selector:
    component: maesh-mesh
  type: ClusterIP
...
```

この 2 つの設定により、`.svc.cluster.local` の変わりに `.maesh` を指定すると、maesh-mesh の Pod が経由されます。  
さらに、maesh-mesh は maesh-controller からホストヘッダ、ポート、バックエンドサーバーのマッピング情報を受け取り設定情報を更新しているので、プロキシ先のバックエンドサーバーをホストヘッダとポートから判断しプロキシできます。

```
 ❯❯ kubectl exec maesh-mesh-2zlxk -- wget -q -O- http://127.0.0.1:8080/api/rawdata | jq '.'
 {
  "routers": {
...
    "test-default-8080-ef3de4bfd3d2b9f0@rest": {
      "entryPoints": [
        "http-5000"
      ],
      "service": "test-default-8080-ef3de4bfd3d2b9f0",
      "rule": "Host(`test.default.maesh`) || Host(`10.111.206.208`)",
      "status": "enabled",
      "using": [
        "http-5000"
      ]
    }
  },
...
  "services": {
    "test-default-8080-ef3de4bfd3d2b9f0@rest": {
      "loadBalancer": {
        "servers": [
          {
            "url": "..."
          }
        ],
        "passHostHeader": true
      },
      "status": "enabled",
      "usedBy": [
        "test-default-8080-ef3de4bfd3d2b9f0@rest"
      ],
      "serverStatus": {
        "xxxx": "UP"
      }
    }
  }
}
```

maesh-mesh のコンテナイメージには [Traefik](https://github.com/containous/traefik) が使われており上記も Traefik の設定フォーマットです。

図もない状態だと若干ややこしいですが、利用方法は簡単です。

いつもとおり **Service を作成し、利用するドメインを変更するだけ**です。

- Service を利用する場合： `<サービス名>.<ネームスペース名>.svc.cluster.local`
- Maesh を利用する場合： `<サービス名>.<ネームスペース名>.maesh`

もちろん Service 経由のアクセスも今までとおり使用できるので、一部の Pod だけアクセス先を変更し試すことも可能です。

では実際に whoami サーバーをデプロイし、maesh-mesh 経由でアクセスできるか確認します。[^3]
この whoami サーバーはリクエストヘッダの内容をレスポンスするサーバーです。

```
# 作業用のネームスペースを作成
 ❯❯ kubectl create namespace whoami && kubectl config set-context $(kubectl config current-context) --namespace=whoami

# whoami サーバーのマニフェストを作成
 ❯❯ cat <<EOF > whoami.yaml
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: whoami
  namespace: whoami
spec:
  replicas: 2
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      serviceAccount: whoami-server
      containers:
      - name: whoami
        image: containous/whoami:v1.4.0
---
apiVersion: v1
kind: Service
metadata:
  name: whoami
  namespace: whoami
  labels:
    app: whoami
spec:
  type: ClusterIP
  ports:
  - port: 80
    name: whoami
  selector:
    app: whoami
EOF

❯❯ kubectl apply -f whoami.yaml
deployment.apps/whoami created
service/whoami created
```

次にクライアントアプリから whoami サーバーに Service と Maesh を使ってアクセスします。

```
# whoami サーバーにアクセスするクライアントアプリを起動しログイン
 ❯❯ kubectl run -it --image=alpine:3.9 --restart=Never client

/ # apk add --no-cache curl

# サービス経由でアクセス
/ # curl http://whoami.whoami.svc.cluster.local
Hostname: whoami-57bcbf7487-tqkdb
IP: 127.0.0.1
IP: ::1
IP: 10.244.2.3
IP: fe80::24e5:dbff:fec9:3b53
RemoteAddr: 10.244.1.6:58896
GET / HTTP/1.1
Host: whoami.whoami.svc.cluster.local
User-Agent: curl/7.64.0
Accept: */*

# Maesh 経由でアクセス
/ # curl http://whoami.whoami.maesh
Hostname: whoami-57bcbf7487-9btlx
IP: 127.0.0.1
IP: ::1
IP: 10.244.1.5
IP: fe80::48f9:3cff:fea6:aeed
RemoteAddr: 10.244.1.2:40270
GET / HTTP/1.1
Host: whoami.whoami.maesh
User-Agent: curl/7.64.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 10.244.1.6
X-Forwarded-Host: whoami.whoami.maesh
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: maesh-mesh-s5fg5
X-Real-Ip: 10.244.1.6

# maesh-mesh の IP アドレスは以下のどちらかとなる
 ❯❯ kubectl -n maesh get po -lcomponent=maesh-mesh -ojson |jq -r '.items[].status.podIP'
10.244.2.2
10.244.1.2
```

Service と同じような感覚で Maesh が利用できました :rocket:  
結果の違いは、RemoteAddr ヘッダーが maesh-mesh IP アドレスになっており、X-Forwarded-xxx ヘッダが追加されている点です。

少し気になったのは、maesh-mesh の前段に Service を経由しているため、クライアントアプリと異なるノード上に配置された maesh-mesh を利用することがあり、DaemonSet なのに ホスト外通信が発生します。
ただこの動作のおかげで DaemonSet でもローリングアップデートが簡単になっている点はメリットだと思います。

## ロードバランシング
次は gRPC がバランシングされるか確認します。
2 つの gRPC のサーバーが起動するようにデプロイし、[ghz](https://github.com/bojand/ghz) ツールを使って負荷をかけます[^4]。

gRPC の場合、Service のアノテーションに `maesh.containo.us/scheme: "h2c"` を追加します。

```
# 作業用のネームスペースを作成
 ❯❯ kubectl create namespace grpc && kubectl config set-context $(kubectl config current-context) --namespace=grpc

# gRPC サーバーのマニフェストを replicas:2 で作成する
 ❯❯ cat <<EOF > greeter-server.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: greeter-server
  namespace: grpc
  labels:
    app: greeter-server
spec:
  replicas: 2
  selector:
    matchLabels:
      app: greeter-server
  template:
    metadata:
      labels:
        app: greeter-server
    spec:
      containers:
      - name: greeter-server
        image: watawuwu/sample-grpc-greeter-server:v2
---
apiVersion: v1
kind: Service
metadata:
  name: greeter-server
  namespace: grpc
  labels:
    app: greeter-server
  annotations:
    maesh.containo.us/scheme: "h2c" # 追加
spec:
  type: ClusterIP
  ports:
  - port: 50051
    name: grpc
  selector:
    app: greeter-server
EOF

# gRPC サーバーをデプロイ
❯❯ kubectl apply -f greeter-server.yaml
deployment.apps/greeter-server created
service/greeter-server created

# デプロイされたことを確認
 ❯❯ kubectl get po
 NAME                              READY   STATUS    RESTARTS   AGE
greeter-server-69874f977c-bq2z4   1/1     Running   0          2m48s
greeter-server-69874f977c-lpp8k   1/1     Running   0          2m48s

```

まずは 1 connection の設定で Service を使いアクセスし、1 つのサーバーにリクエストが偏ることを確認します。

```
# grpc サーバーにアクセスするクライアントアプリを起動しログイン
 ❯❯ kubectl run -it --image=alpine:3.9 --restart=Never client

# ghz をインストール
/ # wget -O- https://github.com/bojand/ghz/releases/download/v0.44.0/ghz_0.44.0_Linux_x86_64.tar.gz  | tar xz -C /usr/local/bin

# プロトコルファイルをダウンロード
/ # wget https://raw.githubusercontent.com/grpc/grpc-go/master/examples/helloworld/helloworld/helloworld.proto

/ # ghz \
     --connections=1 \
     --concurrency=50 \
     --insecure \
     --proto helloworld.proto \
     --call helloworld.Greeter.SayHello \
     --data '{"name":"Joe"}' \
     --total 1000 \
     greeter-server.grpc.svc.cluster.local:50051
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62029/18f06a78-d1fb-715d-6785-22d9f3760fd8.png" width="480px"/>

次に、リクエスト先を Maesh に変更し、1 connection の設定でも 2 つのサーバーにリクエストされることを確認します。

```
/ # ghz \
     --connections=1 \
     --concurrency=50 \
     --insecure \
     --proto helloworld.proto \
     --call helloworld.Greeter.SayHello \
     --data '{"name":"Joe"}' \
     --total 1000 \
     greeter-server.grpc.maesh:50051
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62029/d33665bb-57f5-624f-cb2e-02c97a2f2036.png" width="480px"/>

次は 10 connection に設定を変更します。  
connection が増えれば Service を利用してもバランシングされますが、gRPC サーバーをローリングアップデートすると再びリクエストが偏ります。

```
/ # ghz \
     --connections=10 \
     --concurrency=100 \
     --insecure \
     --proto helloworld.proto \
     --call helloworld.Greeter.SayHello \
     --data '{"name":"Joe"}' \
     --duration 180s \
     greeter-server.grpc.svc.cluster.local:50051
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62029/d0c48033-e2ae-3d9e-1c6b-6f24c4b29afb.png" width="480px"/>

Maesh の場合は以下の通り、ローリングアップデート後もバランシングされています :tada:

```
/ # ghz \
     --connections=10 \
     --concurrency=100 \
     --insecure \
     --proto helloworld.proto \
     --call helloworld.Greeter.SayHello \
     --data '{"name":"Joe"}' \
     --duration 180s \
     greeter-server.grpc.maesh:50051
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62029/0934df94-a031-203d-0f97-b12cbe95a3f2.png" width="480px"/>

## 性能検証

気になる性能ですが 挙動確認の時に記載したとおり、プロキシである maesh-mesh は [Traefik](https://github.com/containous/traefik) を利用しているため、性能自体は Traefik に近いはずです。

Traefik の[公式ドキュメント](https://docs.traefik.io/v1.4/benchmarks/)では、nginx の 85% ほどのトラフィックを処理できると記載されていますが、今回は gRPC ということで条件も大きく変わるため、検証してみます。

今回は比較のために Service の他、Envoy を別途用意しました。

その他にも検証環境を kind から GKE に変更しています。また Helm でデプロイした場合、maesh-mesh に resources.limits が設定されているので、性能検証の妨げとならない程度にリソース上限も緩和します。

```
# リソース上限の緩和
# 負荷試験は GKE で実施するので kubedns を true に
 ❯❯ helm template maesh . \
     --set controller.image.tag=v1.0.0 \
     --set mesh.image.tag=v2.1 \
     --set mesh.resources.limit.mem=2Gi \
     --set mesh.resources.limit.cpu=4000m \
     --set smi.deploycrds=false \
     --set smi.enable=false \
     --set tracing.deploy=false \
     --set tracing.jaeger.enabled=false \
     --set metrics.deploy=false \
     --set metrics.prometheus.enable=false \
     --set kubedns=true \
     | kubectl apply -f-
```

### 性能検証の環境
- Platform: GKE(v1.14.8)
- Node
    - Worker(4 vCPU, 3.6GB memory) x 3
    - OS: CoreOS

### Service

```
/ # ghz \
    --connections=6 \
    --concurrency=200 \
    --insecure \
    --proto helloworld.proto \
    --call helloworld.Greeter.SayHello \
    --data '{"name":"Joe"}' \
    --total 100000 \
    greeter-server.grpc.svc.cluster.local:50051

Summary:
  Count:        100000
  Total:        4.77 s
  Slowest:      66.15 ms
  Fastest:      0.25 ms
  Average:      8.22 ms
  Requests/sec: 20968.39

Response time histogram:
  0.251 [1]     |
  6.841 [53979] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  13.431 [29628]|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  20.020 [10504]|∎∎∎∎∎∎∎∎
  26.610 [3748] |∎∎∎
  33.200 [1474] |∎
  39.790 [463]  |
  46.379 [140]  |
  52.969 [62]   |
  59.559 [0]    |
  66.149 [1]    |

Latency distribution:
  10%% in 2.30 ms
  25%% in 3.82 ms
  50%% in 6.34 ms
  75%% in 10.71 ms
  90%% in 16.60 ms
  95%% in 21.26 ms
  99%% in 31.20 ms

Status code distribution:
  [OK]   100000 responses
```

### Envoy

```
/ # ghz \
    --connections=6 \
    --concurrency=200 \
    --insecure \
    --proto helloworld.proto \
    --call helloworld.Greeter.SayHello \
    --data '{"name":"Joe"}' \
    --total 100000 \
     envoy.grpc.svc.cluster.local:50051


Summary:
  Count:        100000
  Total:        5.47 s
  Slowest:      55.38 ms
  Fastest:      0.60 ms
  Average:      10.29 ms
  Requests/sec: 18283.57

Response time histogram:
  0.603 [1]     |
  6.081 [27227] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  11.559 [40335]|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  17.037 [18958]|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  22.515 [8653] |∎∎∎∎∎∎∎∎∎
  27.993 [3098] |∎∎∎
  33.471 [1020] |∎
  38.949 [506]  |∎
  44.427 [120]  |
  49.905 [53]   |
  55.383 [29]   |

Latency distribution:
  10%% in 4.16 ms
  25%% in 5.87 ms
  50%% in 8.63 ms
  75%% in 13.21 ms
  90%% in 18.75 ms
  95%% in 22.33 ms
  99%% in 30.96 ms

Status code distribution:
  [OK]   100000 responses
```

### Maesh

```
/ # ghz \
    --connections=6 \
    --concurrency=200 \
    --insecure \
    --proto helloworld.proto \
    --call helloworld.Greeter.SayHello \
    --data '{"name":"Joe"}' \
    --total 100000 \
     greeter-server.grpc.maesh:50051

Summary:
  Count:        100000
  Total:        11.40 s
  Slowest:      128.42 ms
  Fastest:      0.72 ms
  Average:      20.74 ms
  Requests/sec: 8774.54

Response time histogram:
  0.718 [1]     |
  13.489 [37379]|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  26.259 [34287]|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  39.030 [17636]|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  51.801 [6926] |∎∎∎∎∎∎∎
  64.571 [2645] |∎∎∎
  77.342 [825]  |∎
  90.113 [249]  |
  102.884 [46]  |
  115.654 [4]   |
  128.425 [2]   |

Latency distribution:
  10%% in 5.96 ms
  25%% in 10.10 ms
  50%% in 17.57 ms
  75%% in 27.91 ms
  90%% in 39.94 ms
  95%% in 48.81 ms
  99%% in 65.74 ms

Status code distribution:
  [OK]   100000 responses
```

パラメータのチューニングなしだと以下の結果となります。

|         | Requests/sec | AVG Latency(ms) | 99% Latency(ms) |
|---------|--------------|-----------------|-----------------|
| Service | 20968        | 8.22            | 31.20           |
| Envoy   | 18283        | 10.29           | 30.96           |
| Maesh   | 8774         | 20.74           | 65.74           |

性能要件が厳しい場合、検討段階で実環境での計測が必要ですね。  
[`Benchmarking 5 Popular Load Balancers`](https://www.loggly.com/blog/benchmarking-5-popular-load-balancers-nginx-haproxy-envoy-traefik-and-alb/) の HTTPS ベンチマークでも、Envoy と Traefik の差があるので HTTPS の処理部分が苦手かもしれません。

## Service Mesh Interface(SMI)

Maesh は [Service Mesh Interface](https://smi-spec.io/)（以後 SMI）にも対応しているため、SMI の Traffic Access Control や Traffic Split が利用できます。

```
# SMI を有効に
 ❯❯ helm template maesh . \
     --set controller.image.tag=v1.0.0 \
     --set mesh.image.tag=v2.1 \
     --set smi.deploycrds=true \
     --set smi.enable=true \
     --set tracing.deploy=false \
     --set tracing.jaeger.enabled=false \
     --set metrics.deploy=false \
     --set metrics.prometheus.enable=false \
     | kubectl apply -f-


# CRD が作成されているか確認
 ❯❯ kubectl get crd
NAME                                CREATED AT
httproutegroups.specs.smi-spec.io   2019-11-23T13:58:22Z
tcproutes.specs.smi-spec.io         2019-11-23T13:58:22Z
trafficsplits.split.smi-spec.io     2019-11-23T13:58:22Z
traffictargets.access.smi-spec.io   2019-11-23T13:58:22Z
```

CRD がデプロイされると以下のようなマニフェストが適用できます。

```
apiVersion: split.smi-spec.io/v1alpha1
kind: TrafficSplit
metadata:
  name: server-split
  namespace server
spec:
  service: server
  backends:
  - service: server-v1
    weight: 80
  - service: server-v2
    weight: 20
```

現在のところ、特定のメトリクスやスケジューラーによって weight を自動更新する機能（ロールバック含む）がありません。

実際の運用では [argo-rollouts](https://github.com/argoproj/argo-rollouts) などの別の仕組みやスクリプトが必要になりそうです。

## Tracing と Metrics
その他にも Tracing(Jaeger) と Metrics(Prometheus) にも対応しています。

Helm で用意されている Prometheus や Jaeger を利用しても良いですが、すでに自分のクラスタに Prometheus や Jaeger が稼働している場合にはそちらを利用することも可能です。

```
# Tracing と Metrics を有効にする
 ❯❯ helm template maesh . \
     --set controller.image.tag=v1.0.0 \
     --set mesh.image.tag=v2.1 \
     --set smi.deploycrds=false \
     --set smi.enable=false \
     --set tracing.deploy=true \
     --set tracing.jaeger.enabled=true \
     --set metrics.deploy=true \
     --set metrics.prometheus.enable=true \
     --set metrics.storageClass=standard \ # 必要に応じて変更してください
     | kubectl apply -f-

# Prometheus, Grafana, Jaeger が作成されます
 ❯❯ kubectl get po
NAME                                READY   STATUS    RESTARTS   AGE
grafana-core-7cb56c69cb-2zjwm       1/1     Running   0          44s
jaeger-787f575fbd-nh98f             1/1     Running   0          3m48s
maesh-controller-5b765cb856-v4rbh   1/1     Running   0          41m
maesh-mesh-dqr99                    1/1     Running   0          3m46s
prometheus-core-996d966fb-gqh9z     2/2     Running   0          44s
```

- Grafana

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62029/7af75577-a27f-becb-dc9e-167cf386b498.png" width="640px"/>

- Jaeger

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62029/412bde52-54b0-1859-52ba-61a90ca5578f.png" width="640px"/>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62029/f684dfac-04b1-ecef-369c-a4b749be3049.png" width="640px"/>

トラフィック関連のメトリクスはある程度用意されているので、問題が発生しているマイクロサービスの特定も簡単になりそうです。

### 最後に
私が最初に Service Mesh と出会ったのは 2016 年の Linkerd のスライドでした。  
gRPC を使ったマイクロサービスの Web アプリケーションを構築するために、`gRPC をロードバランシングしたい`という動機のために行き着いたスライドでした。
当時は機能と構成がシンプルでリソースの使用量以外に不満はありませんでした。

しかし 2019 現在の Service Mesh という言葉は、ネットワークのインフラ部分を支える大きな技術として成長しているようです。
そのため導入に求められる知識が多く、規模の小さいアプリケーション開発者にとっては敷居が高いものになってしまったと感じています。

そもそも gRPC のロードバランシングだけなら Envoy などの Proxy の導入で解決する方がシンプルです。  
ただ、少ない設定で Failover などの機能も手に入る Maesh のような、シンプルな Service Mesh を望んでいる組織やチームもあるのではないでしょうか。

もしかしたら Service Mesh は `こういうのでいいのかも？`

[^1]: [サイドカーの停止タイミングを別のライフサイクルにする提案があります](https://github.com/kubernetes/enhancements/blob/master/keps/sig-apps/sidecarcontainers.md)
[^2]: [ノード間は mTLS の対応は Issue があります](https://github.com/containous/maesh/issues/213)
[^3]: [現状では別のネームスペースを用意する必要があります](https://github.com/containous/maesh/pull/348)
[^4]: Service には複数の port を設定してはいけない？ 現状ではアクセスした port とは関係なく Service に登録されている全ての port をラウンドロビンするので注意が必要です