Rio v0.0.3はKubernetes/Istioをどう利用しているの?

Rio v0.0.3がリリースされましたね。

github.com

以前書いたRioの記事ではスタンドアロン版を試しましたので、 今回は既存KubernetesクラスタとしてDocker for Macを利用してRioを試してみようと思います。

前回と重複する内容も含まれますが、今回はRioKubernetes/Istioをどう利用しているのかといった点を中心にみていきたいと思います。
(前回の記事でIstio周りもうちょい詳しく知りたかったという声もあったような気がしますので)

前回の記事(スタンドアロン版のRio)

febc-yamamoto.hatenablog.jp

必要なもの

おそらくDocker for Windowsでも動くと思いますが手元に環境がないため今回は試してません。

また、大事な注意点として、Rio v0.0.3時点では簡単にアンインストールする手段は提供されていません!!

Docker for Macをファクトリーリセットするか地道に手で一つ一つ消していくくらいしかアンインストール方法がないため、お手元で試される方はご注意ください。


<宣伝>
クラウド上に使い捨てKubernetesクラスタを建てる場合はTerraform+RKEがおすすめです。

github.com

Rioのインストール(Mac)

まずは手元のMacRioをインストールしておきます。
RioはGoで書かれたシングルバイナリですのでダウンロード&展開&PATHの通った場所に配置すればOKです。

#rio v0.0.3のダウンロード
$ curl -L -o rio.tgz https://github.com/rancher/rio/releases/download/v0.0.3/rio-v0.0.3-darwin.tar.gz

#展開
$ tar zxvf rio.tgz 

#PATHの通った場所に移動
$ sudo mv rio-v0.0.3-darwin/rio /usr/local/bin/

#確認
$ rio -v

# "rio version v0.0.3" と表示されればOK

Rioサーバ側コンポーネントのデプロイ

Rioスタンドアロン版の場合はrio serverコマンドを実行することでembedなk8s(k3sとか呼ばれてるやつ)を起動してそこに各種コンポーネントをデプロイします。
既存のk8sクラスタを利用する場合はkubectlの向き先に対してデプロイを行います。今回はこちらを利用します。

kubectlコマンドの接続先の確認

始める前にkubectlの向き先がDocker for Macになっているか確認しておいてください。

$ kubectl get nodes

NAME                 STATUS    ROLES     AGE       VERSION
docker-for-desktop   Ready     master    2d        v1.10.3

NAMEの部分がdocker-for-desktopになっていればOKですね。

現時点ではRioのデプロイを行うにはcluster-admin権限が必要です。
この辺は今後改善されるっぽいです。

Rioコンポーネントのデプロイ

それではrio loginコマンドを実行してみましょう。

$ rio login

[1] Connect to remote Rio server
[2] Install Rio in existing Kubernetes
Select Number [1] 

こんな感じで選択肢が表示されます。今回は既存のk8sクラスタにデプロイしますので2を選択してください。

その後以下のようなログが表示されるはずです。

INFO[0068] Installing Rio                               
INFO[0071] Waiting to connect to Rio                    
INFO[0111] Log in successful  

Log in successfulと表示されるまで数分かかるかもしれません。気長に待ちましょう。 (手元のマシンでは1分ほどでした)

Rioを使ってみる

コンテナ起動 & 確認

まずは動かしてみます。 docker runするような感覚でrio runを実行することでコンテナの起動が可能です。
ここではDockerHubのnginxイメージを利用してfoobarというサービスを起動します。

Rioでは「サービス」という概念があります。k8sやDockerでのserviceとは役割が異なり、単なる同じ役割を持つコンテナの集合ということです。 詳細はREADME.mdのServiceセクションを参照してください。

#DockerHubのnginxイメージを用いてコンテナ起動
$ rio run --name foobar nginx 

default-265db573:foobar  # k8s上でのネームスペース:サービス名が表示される

#起動しているか確認
$ rio ps

NAME      IMAGE     CREATED              SCALE     STATE     ENDPOINT   DETAIL
foobar    nginx     About a minute ago   1         active               

#foobarサービスのコンテナを確認
$ rio ps foobar

NAME                     IMAGE     CREATED         NODE                 IP          STATE     DETAIL
foobar/f6985f578-schpv   nginx     2 minutes ago   docker-for-desktop   10.1.3.58   running   

起動したようですね。 dockerコマンドやkubectlコマンドなどと同じく、コンテナへのattachやexecも可能です。

# bashを実行(--interactive + --ttyオプションを指定 = dockerと同じ)
$ rio exec -it foobar bash

root@foobar-f6985f578-schpv:/# 

# 以下のようにサービス名/コンテナ名まで指定してもOK
# rio exec -it foobar/f6985f578-schpv bash

次にk8sからはどう見えてるか確認してみます。

kubectlでKubernetes側から確認

# ネームスペースの確認
$ kubectl get namespaces

NAME               STATUS    AGE
default            Active     2d
default-265db573   Active    10m  # rio runで作成されたネームスペース
docker             Active     2d
istio-095b8502     Active    15m 
kube-public        Active     2d
kube-system        Active     2d
rio-defaults       Active    15m 
rio-system         Active    16m

Rioがいくつかnamespaceを作成してますね。rio run実行後に表示されたdefault-265db573というものも見えます。
実際のコンテナはこのネームスペースに配置されています。

それではこのネームスペースに何があるのか確認していきましょう。
実際に手元で試す際はdefault-265db573の部分をそれぞれの手元で表示されたものに置き換えてください。

$ kubectl get all -n default-265db573

NAME                         READY     STATUS    RESTARTS   AGE
pod/foobar-f6985f578-schpv   2/2       Running   0          14m

NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/foobar   ClusterIP   10.97.38.245   <none>        80/TCP    14m

NAME                     DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/foobar   1         1         1            1           14m

NAME                               DESIRED   CURRENT   READY     AGE
replicaset.apps/foobar-f6985f578   1         1         1         14m

ServiceとDeploymentが作成され、そこからReplicaSetとPodも生えてますね。
それぞれ少し詳しくみておきます。

# Serviceの確認
$ kubectl get svc -o wide -n default-265db573
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE       SELECTOR
foobar    ClusterIP   10.97.38.245   <none>        80/TCP    19m       app=foobar,rio.cattle.io/namespace=default-265db573,rio.cattle.io/service=foobar,rio.cattle.io=true

# Deploymentの確認
$ kubectl get deploy -o wide -n default-265db573 
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE       CONTAINERS           IMAGES                                    SELECTOR
foobar    1         1         1            1           20m       foobar,istio-proxy   nginx,docker.io/istio/proxy_debug:1.0.0   app=foobar,rio.cattle.io=true,rio.cattle.io/namespace=default-265db573,rio.cattle.io/revision=latest,rio.cattle.io/service=foobar

# Podの確認
$ kubectl get pod -n default-265db573
NAME                     READY     STATUS    RESTARTS   AGE
foobar-f6985f578-schpv   2/2       Running   0          22m

# Podの詳細
$ kubectl describe pod -n default-265db573 foobar-f6985f578-schpv

Name:           foobar-f6985f578-schpv
Namespace:      default-265db573
Node:           docker-for-desktop/192.168.65.3
Labels:         app=foobar
                pod-template-hash=925419134
                rio.cattle.io=true
                rio.cattle.io/namespace=default-265db573
                rio.cattle.io/revision=latest
                rio.cattle.io/service=foobar
Status:         Running
Init Containers:
  istio-init:
    # ...
  enable-core-dump:
   # ...
Containers:
  foobar:
    Image:          nginx
    Port:           <none>
    Host Port:      <none>
    # ...
  istio-proxy:
    Image:         docker.io/istio/proxy_debug:1.0.0
    Mounts:
      /etc/certs/ from istio-certs (ro)
      /etc/istio/proxy from istio-envoy (rw)
    # ...
Volumes:
  istio-envoy:
    Type:    EmptyDir (a temporary directory that shares a pod s lifetime)
    Medium:  Memory
  istio-certs:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  istio.default
    Optional:    true

Events:
  Type    Reason                 Age   From                         Message
  ----    ------                 ----  ----                         -------
  Normal  Created                23m   kubelet, docker-for-desktop  Created container
#...

Istio+指定したnginxが起動してますね。

次にRioでサービス内のコンテナをスケールアウトさせてみましょう。

スケールアウトさせてみる

rio scaleコマンドでスケールさせることが出来ます。

$ rio scale foobar=3
default-265db573:foobar

#確認
$ rio ps
NAME      IMAGE     CREATED          SCALE     STATE     ENDPOINT   DETAIL
foobar    nginx     38 minutes ago   3         active               

#詳細確認
$ rio ps foobar
NAME                     IMAGE     CREATED              NODE                 IP          STATE     DETAIL
foobar/f6985f578-wbgwj   nginx     About a minute ago   docker-for-desktop   10.1.3.60   running   
foobar/f6985f578-nrmhk   nginx     About a minute ago   docker-for-desktop   10.1.3.59   running   
foobar/f6985f578-schpv   nginx     39 minutes ago       docker-for-desktop   10.1.3.58   running   

Kubernetes側もみておきます。

#Deploymentの確認
$ kubectl get deploy -n default-265db573
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
foobar    3         3         3            3           40m

#Podの確認
$ kubectl get pod -n default-265db573
NAME                     READY     STATUS    RESTARTS   AGE
foobar-f6985f578-nrmhk   2/2       Running   0          2m
foobar-f6985f578-schpv   2/2       Running   0          41m
foobar-f6985f578-wbgwj   2/2       Running   0          2m

きちんとスケールアウトされてました。

サービスの削除

削除はrmサブコマンドで行います。

#削除
$ rio rm foobar
default-265db573:foobar

#確認
$ rio ps
NAME      IMAGE     CREATED   SCALE     STATE     ENDPOINT   DETAIL

外部からアクセス可能にしてみる(expose相当)

次に起動したサービスを外部からアクセス可能にしてみます。 外部からアクセス可能にするにはrio run時に-p(--publish)オプションを指定します。
(rio editなどでもOK)

# run時にオプションを指定して80番ポートを公開
$ rio run --name foobar -p 80/http nginx
default-265db573:foobar

# 確認
$ rio ps
NAME      IMAGE     CREATED         SCALE     STATE     ENDPOINT                                 DETAIL
foobar    nginx     8 seconds ago   1         active    http://foobar.default.127.0.0.1.nip.io   

今度はENDPOINTというのが割り当てられましたね。 この辺は前回の記事で説明した通りノードのIPアドレスにを指すようなFQDNが割り振られます。

起動したらブラウザなどでENDPOINTのURLを開いてみましょう。

f:id:febc_yamamoto:20181003013156p:plain

非常に簡単に公開できましたね。

Kubernetes側からのぞいてみる(外部からのアクセス編)

またKubernetes側を確認してみます。 まずはServiceです。

$ kubectl get svc -n default-265db573 -o wide
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE       SELECTOR
foobar    ClusterIP   10.97.38.245   <none>        80/TCP    1h        app=foobar,rio.cattle.io/namespace=default-265db573,rio.cattle.io/service=foobar,rio.cattle.io=true

前と変わってないですね。RioはIstioを使ってますのでそちらの方を確認なければいけませんね。 なおIstioについては以下を読んでおくと以降をスムーズに読めると思います。

istio.io

# Istio Gatewayの確認
$ kubectl get gateways --all-namespaces
NAMESPACE        NAME          AGE
istio-095b8502   rio-gateway   1h

# Gatewayの詳細確認
$ kubectl get gateways -n istio-095b8502 rio-gateway -o yaml
#適当に省略してます
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: rio-gateway
  namespace: istio-095b8502
spec:
  selector:
    gateway: external
  servers:
  - hosts:
    - '*'
    port:
      number: 80
      protocol: HTTP

ホスト名問わず80番ポートへのアクセスを扱うようになってますね。
セレクタに指定されているgateway: externalについても確認しておきます。

#全ネームスペースからgateway: externalというラベルを持つサービスを取得
$ kubectl get svc --all-namespaces -l gateway=external

NAMESPACE        NAME      TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
istio-095b8502   rio-lb    LoadBalancer   10.102.122.149   localhost     80:31752/TCP   1h

Type: LoadBalancerなServiceが作成されてますね。これで外部からのアクセスを受け付けています。
(実はDocker for Mac18.03.0-ce-rc1-mac54以降でType: LoadBalancerが利用できるようになってたりします)

docs.docker.com

次にIstioのVirtualServiceについてみてみます。

$ kubectl get virtualservices -n default-265db573
NAME      AGE
foobar    52m

#詳細確認
$ kubectl get virtualservices -n default-265db573 -o yaml foobar
# 適当に省略してます
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: foobar
  namespace: default-265db573
spec:
  gateways:
  - mesh
  - rio-gateway.istio-095b8502.svc.cluster.local
  hosts:
  - foobar
  - foobar:80
  - foobar.default.127.0.0.1.nip.io
  - foobar.default.127.0.0.1.nip.io:80
  http:
  - match:
    - gateways:
      - mesh
      - rio-gateway.istio-095b8502.svc.cluster.local
      port: 80
    route:
    - destination:
        host: foobar # foobar.default-265db573.svc.cluster.localと解釈される
        port:
          number: 80
        subset: latest
      weight: 100

先ほど確認したGatewayrio-gatewayにバインドされていますね。
また、ホスト名としてfoobarfoobar.default.127.0.0.1.nip.ioが指定されています。
後者は先ほどrio psで確認したENDPOINTに表示されていたものですね。

destinationも指定されているようです。latestという名前のsubset宛てですね。
IstioのDestinationRuleも確認してみます。

$ kubectl get destinationrules -n default-265db573
NAME      AGE
foobar    1h

#詳細確認
$ kubectl get destinationrules -n default-265db573 -o yaml foobar
#適当に省略してます
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: foobar
  namespace: default-265db573
spec:
  host: foobar
  subsets:
  - labels:
      rio.cattle.io/revision: latest
    name: latest

latestという名前のsubsetが定義されています。
rio.cattle.io/revision: latestというラベルを持つPod宛てということですね。

#宛先Podの確認
$ kubectl get pod -l rio.cattle.io/revision=latest -n default-265db573
NAME                      READY     STATUS    RESTARTS   AGE
foobar-6554b575b7-fz7fz   2/2       Running   0          1h

ということでrio runの裏ではIstioを上手く使って頑張っているようです。

次はカナリアリリースを試してみます。

カナリアリリース

Rioカナリアリリースをサポートしています。 先ほどはfoobarサービスとしてnginxイメージを利用しましたので、Apache(httpd)を使うように更新しカナリアリリースしてみます。

まずfoobarサービスに対するステージング環境をデプロイします。

#Apache(httpd)イメージを指定、foobar:v2とする
$ rio stage --image=httpd foobar:v2

#確認
# rio ps
NAME        IMAGE     CREATED          SCALE     STATE     ENDPOINT                                    DETAIL
foobar      nginx     40 minutes ago   1         active    http://foobar.default.127.0.0.1.nip.io      
foobar:v2   httpd     40 minutes ago   1         active    http://foobar-v2.default.127.0.0.1.nip.io   

foobar:v2がデプロイされました。ENDPOINTをブラウザで開いて確認しておきます。

f:id:febc_yamamoto:20181003013646p:plain

昔懐かしい"It works!"が表示されていますね。


続いて早速カナリアリリースしてみます。
元のfoobarサービスのENDPOINTに対してアクセスした際に30%の確率で更新版であるfoobar:v2が表示されるようにしてみます。

$ rio weight foobar:v2=30%

foobarのエンドポイントhttp://foobar.default.127.0.0.1.nip.ioにアクセスし何度かリロードしてると表示が切り替わるのが確認できるはずです。
(キャッシュに注意してくださいね)

では裏ではどうなってるのか、Kubernetes側を確認してみましょう。

Kubernetes側からのぞいてみる(カナリアリリース編)

まずVirtualServicesを確認してみます。(ちなみにGatewayは変更なし)

$ kubectl get virtualservices -n default-265db573
NAME        AGE
foobar      1h
foobar-v2   9m

予想通りfoobar-v2が増えてますね。 これは先ほどまでで確認したfoobarと同等ですので省略し、カナリアリリースしているfoobarの方がどう変わったか詳しく見ておきます。

$ kubectl get virtualservices -n default-265db573 -o yaml foobar
#適当に省略してます
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: foobar
  namespace: default-265db573
spec:
  gateways:
  - mesh
  - rio-gateway.istio-095b8502.svc.cluster.local
  hosts:
  - foobar
  - foobar:80
  - foobar.default.127.0.0.1.nip.io
  - foobar.default.127.0.0.1.nip.io:80
  http:
  - match:
    - gateways:
      - mesh
      - rio-gateway.istio-095b8502.svc.cluster.local
      port: 80
    route:
    - destination:
        host: foobar
        port:
          number: 80
        subset: latest
      weight: 70
    - destination:
        host: foobar
        port:
          number: 80
        subset: v2
      weight: 30

spec.http.routeの子要素が追加されてますね。

指定通りv2の方は30%、latestは70%となっています。

念のためDestinationRulesの方も載せておきます。

$ kubectl get destinationrules -n default-265db573 -o yaml foobar
#適当に省略してます
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: foobar
  namespace: default-265db573
spec:
  host: foobar
  subsets:
  - labels:
      rio.cattle.io/revision: latest
    name: latest
  - labels:
      rio.cattle.io/revision: v2
    name: v2

こちらでもRioがIstioを上手く使って頑張っているようですね。

ステージング => 本番へ昇格(プロモート)させる

最後にfoobar:v2を昇格させfoobarの代わりにfoobar:v2を利用するようにしてみます。

$ rio promote foobar:v3

# 確認
$ rio ps
NAME      IMAGE     CREATED             SCALE     STATE     ENDPOINT                                 DETAIL
foobar    httpd     About an hour ago   1         active    http://foobar.default.127.0.0.1.nip.io   

イメージがhttpdに変わってますね。
v2というラベルも無くなっているのもポイントです。

#プロモートするとタグは無くなる
$ kubectl get pod -l rio.cattle.io/revision=v2 -n default-265db573 
No resources found.

#その代わりにこれまでのv2にはlatestというタグが改めて付与される
$ kubectl get pod -l rio.cattle.io/revision=latest -n default-265db573 
NAME                      READY     STATUS    RESTARTS   AGE
foobar-69c4b7944c-2jcxt   2/2       Running   0          1m

まとめ

ということで今回はRioKubernetes/Istioをどう扱っているかについてみてみました。

まだまだ不安定な部分も結構あったりしますが、今もGitHub大きめのPRが上がってきてたりと開発は進んでる様子なので今後に期待しています。

以上です。

Kubernetes完全ガイド (impress top gear)

Kubernetes完全ガイド (impress top gear)

Docker/Kubernetes 実践コンテナ開発入門

Docker/Kubernetes 実践コンテナ開発入門