ConftestでOpenPolicyAgent/Regoを使いTerraformのコードにポリシーを適用してみる

今日はConftestを用いてTerraformでのインフラコードにポリシーを適用してみます。

TerraformでのインフラコードのUnitTest

terraform validateでの構文チェック

Terraformではtfファイルの構文チェックを行ってくれるterraform validateコマンドが提供されています。 実行するとtfファイルの構文誤りやパラメータ名間違いなどを検出してくれます。

$ terraform validate

Error: Unsupported argument ← パラメータ名間違い

  on test.tf line 6, in data "sakuracloud_server" "server":
   6:   name_selectorsa = ["sakura-dev"]

An argument named "name_selectorsa" is not expected here. Did you mean
"name_selectors"?

これを利用すれば最低限tfファイルとして正しく書けているかのテストが行えます。

特にTerraform v0.12からは以下のように変数に型情報を与えることが出来るようになっており、モジュール利用時などにより厳密なチェックが行えるようになりました。

# var.nodesはaddressとuserというフィールドを持つオブジェクトのリストしか指定できない
variable "nodes" {
  type = list(object({
    address = string,
    user    = string,
  }))
}

ポリシーの適用

テストの際、terraform validateでの構文チェック以外にも様々な制約を課したい/ポリシーを適用したいことが多々あります。

例えば、

といった場合です。

Terraform EnterpriseであればSentinelを用いてポリシーの適用が行えるのですが、OSS版の場合、現時点ではSentinelを利用できません。

そこで今回はConftestを用いてポリシーの適用を行ってみました。

Conftest

Conftestについてはこちらの記事が詳しいです。

kenfdev.hateblo.jp

ConftestはYAMLあるいはJSONで定義された設定ファイルに対してテストを書けるというツールです。
面白いのは、テストに使うのがOpen Policy AgentのRegoというポリシー用の言語だという点です。

ConftestでOpenPolicyAgent/Regoを用いてポリシーを定義しておいてCIでConftestを実行すればポリシーの適用/強制ができそうです。

ConftestのリポジトリにはTerraformのコードをテストする例がありますのでそちらを参考にポリシーを書いてみます。

github.com

conftestでのterraformインフラコードのテスト

Conftestはコマンドラインツールとなっており、以下のように実行することでYAML/JSONファイルのテストが行えます。

$ conftest test <file>

デフォルトではカレントディレクトリのpolicyディレクトリ配下を参照するようになっていますのでこちらにポリシーファイル(.rego)を作成していきます。 (この挙動は-pまたは--policyオプションで変更可能です)

tfファイルをどうやってテストするの?

TerraformでのインフラコードはJSONでも記載できますが、通常はHCL(.tf)で記載していると思います。 conftestはHCLを読んでくれませんのでどうにかしてJSON/YAMLに変換する必要があります。

ConftestのリポジトリにあるTerraformの例ではplanファイルを出力した上でterraform showJSON出力オプションを指定して実行することでtfファイルを間接的にテストするという方法を取っています。

例えば、以下のようなtfファイルがある場合、

resource sakuracloud_server "server" {
  name   = "example"
  core   = 1
  memory = 1
}

これを元にplanファイル生成〜terraform showJSON出力すると以下のようになります。

# planファイルを出力
$ terraform plan --out plan.tfplan

# terraform showをJSONで出力
$ terraform show -json plan.tfplan | jq .
{
  "format_version": "0.1",
  "terraform_version": "0.12.1",
  "planned_values": {
    "root_module": {
      "resources": [
        {
          "address": "sakuracloud_server.server",
          "mode": "managed",
          "type": "sakuracloud_server",
          "name": "server",
          "provider_name": "sakuracloud",
          "schema_version": 1,
          "values": {
            "additional_nics": null,
            "commitment": "standard",
            "core": 2,
            "description": null,
            "disable_pw_auth": null,
            "graceful_shutdown_timeout": 60,
            "hostname": null,
            "icon_id": null,
            "interface_driver": "virtio",
            "memory": 4,
            "name": "test",
            "nic": "shared",
            "note_ids": null,
            "password": null,
            "private_host_id": null,
            "ssh_key_ids": null
          }
        }
      ]
    }
  },
  "resource_changes": [
    {
      "address": "sakuracloud_server.server",
      "mode": "managed",
      "type": "sakuracloud_server",
      "name": "server",
      "provider_name": "sakuracloud",
      "change": {
        "actions": [
          "create"
        ],
        "before": null,
        "after": {
          "additional_nics": null,
          "commitment": "standard",
          "core": 2,
          "description": null,
          "disable_pw_auth": null,
          "graceful_shutdown_timeout": 60,
          "hostname": null,
          "icon_id": null,
          "interface_driver": "virtio",
          "memory": 4,
          "name": "test",
          "nic": "shared",
          "note_ids": null,
          "password": null,
          "private_host_id": null,
          "ssh_key_ids": null
        },
        "after_unknown": {
          "additional_display_ipaddresses": true,
          "cdrom_id": true,
          "disks": true,
          "display_ipaddress": true,
          "dns_servers": true,
          "gateway": true,
          "id": true,
          "ipaddress": true,
          "macaddresses": true,
          "nw_address": true,
          "nw_mask_len": true,
          "packet_filter_ids": true,
          "private_host_name": true,
          "tags": true,
          "vnc_host": true,
          "vnc_password": true,
          "vnc_port": true,
          "zone": true
        }
      }
    }
  ],
  "configuration": {
    "root_module": {
      "resources": [
        {
          "address": "sakuracloud_server.server",
          "mode": "managed",
          "type": "sakuracloud_server",
          "name": "server",
          "provider_config_key": "sakuracloud",
          "expressions": {
            "core": {
              "constant_value": 2
            },
            "memory": {
              "constant_value": 4
            },
            "name": {
              "constant_value": "test"
            }
          },
          "schema_version": 1
        }
      ]
    }
  }
}

これをconftestコマンドに読ませることでテストを行います。

ポリシーの作成

今回は試しにサーバのコア数は2以上、メモリは4GB以上を指定しないといけないというポリシーにしてみます。

ポリシーファイルは以下の内容でpolicy/instance_type.regoというファイルを作成します。

package main

# コア数は2以上であること
deny[msg] {
  resource := input.resource_changes[index]
  resource.type == "sakuracloud_server"
  resource.change.after.core < 2
  msg = "sakuracloud_server.core must be greater than 2"
}

# メモリは4GB以上であること
deny[msg] {
  resource := input.resource_changes[index]
  resource.type == "sakuracloud_server"
  resource.change.after.memory < 4
  msg = "sakuracloud_server.memory must be greater than 4"
}

テスト実行(ポリシー適用)

それでは早速conftestでテストを実行(ポリシーを適用)してみます。

Makefileの作成

プランファイルの出力~JSONへの変換は毎回コマンド入力するのも大変なのでMakefileでまとめておきます。

NAME := myproject

COMMAND := terraform
PLAN = $(NAME)-plan.tfplan
SHOW = $(NAME)-show.json
CODE = $(NAME).tf


all: test

plan: $(PLAN)

$(PLAN): $(CODE)
    $(COMMAND) plan -out $(PLAN)

show: $(SHOW)

$(SHOW): plan
    $(COMMAND) show -json $(PLAN) > $(SHOW)

test: show
    cat $(SHOW) | conftest test -

clean:
    @rm -f $(PLAN) $(SHOW)

.PHONY: plan show test all clean

これでmakeするだけでテスト可能になります。 なお、このMakefileだと中間ファイルが作成されますので必要に応じてcleanを実行したり.gitignoreに追記したりしておいてください。

実行

現時点でのtfファイルは以下の通りです。

resource sakuracloud_server "server" {
  name   = "example"
  core   = 1
  memory = 1
}

ポリシーである2コア以上かつ4GBメモリ以上に反しているのでエラーとなるはずです。

$ make

# [中略]
cat example-show.json | conftest test -
   sakuracloud_server.core must be greater than 2
   sakuracloud_server.memory must be greater than 4
make: *** [test] Error 1

エラーになりましたね!

今度はtfファイルを修正して実行してみます。

resource sakuracloud_server "server" {
  name   = "example"
  core   = 2
  memory = 4
}
# [中略]

cat example-show.json | conftest test -

今度はエラーとなりませんでしたね。

あとは必要に応じてポリシーを充実させ、CIに組み込めば良さそうです。

終わりに

Open Policy Agent/Regoを初めて使ってみましたがなかなか面白いですね。 TerraformのようなインフラコードのテストでShift left testing出来ると効率がグッと上がりますのでどんどん活用していきたいです。

以上です。

参考情報

TerraformでのプロビジョニングにVNCを使う

TerraformでVNCでのプロビジョニングを行えるようにするプラグインterraform-provisioner-vncを公開しました。

github.com

Terraformでのプロビジョニング

Terraformではリソースの初期設定や削除時のクリーンアップ処理などを行えるようにプロビジョナーという仕組みが用意されています。

www.terraform.io

Terraform v0.12.1の時点では以下のようなプロビジョナーが利用可能となっています。

  • chef Provisioner
  • file Provisioner
  • habitat Provisioner
  • local-exec Provisioner
  • remote-exec Provisioner
  • salt-masterless Provisioner

SSH/WinRMが利用できないリソースのプロビジョニングは?

プロビジョナーのうちlocal-exec以外はSSHまたはWinRM(SSHのみの場合もある)でリモートに接続してなんらかの処理を行うようになっています。 これはSSH/WinRMが利用できないリソース、例えば初期状態ではリモート接続を許可していないようなリソースではこれらを利用できないということです。

この問題の解決方法の一つとして、VNC経由でコマンドを実行するのが今回公開したterraform-provisioner-vncです。 Packerでいうboot_commandを実行するものとなっており、主にSSH/WinRM出来るまでの環境を整えることを目的としています。

terraform-provisioner-vncの使い方

通常のプロビジョナーと同じく、対象のリソースやnull_resourceにprovisionerブロックを記載することで利用します。 例えばさくらのクラウドのサーバだと以下のように使います。

resource sakuracloud_server "example" {
  name     = "example"
  # ...中略

  provisioner vnc {
    host      = self.vnc_host
    port      = self.vnc_port
    password  = self.vnc_password
    boot_wait = "10s"

    # ここに実行したいコマンドを記載していく
    inline = [
      "<tab><wait>",
      "text ks=http://${var.ip}:${var.port}/anaconda-ks.cfg<enter>",
    ]
  }
}

remote-execと同じく、inline(文字列リストで指定)、script(スクリプトファイルのパスで指定)、またはscripts(ファイルパスのリストで指定)で実行するスクリプトを指定できます。
記載できる内容はPackerでのboot_commandと互換性があり、<enter>などの特殊キーもサポートしています。(入力処理はPackerそのものを利用してます)

指定できるスクリプトについてはPackerのQEMU Builderのboot_commandについてのドキュメントを参照してください。

www.packer.io

terraform-provisioner-vncのインストール

リリースページからバイナリファイルをダウンロードし~/.terraform.d/plugins配下に配置すればOKです。

利用例

利用例として、さくらのクラウド上のサーバでk3OSを起動〜ディスクへのインストールまでを行う例を挙げておきます。

参考: k3os on さくらのクラウド - febc技術メモ

k3OSをISOイメージから起動した場合、公開鍵を登録する or rancherユーザーのパスワードを設定するまでSSH接続できません。
このため、VNCプロビジョナーを用いてOSのインストール処理〜GitHubから公開鍵の取得/登録などを行います。
OSのインストール後はSSHプロビジョナーで接続して任意のプロビジョニングが実行できます。 この例ではkubectlコマンドを実行してみています。

variable server_name {
  default = "example"
}

variable github_username {
  default = "yamamoto-febc"
}

provider sakuracloud {
  zone = "is1b"
}

data sakuracloud_cdrom k3os {
  name_selectors = ["k3OS"]
}

resource sakuracloud_disk "disk" {
  name = var.server_name
}

resource sakuracloud_server "k3os" {
  name     = var.server_name
  core     = 2
  memory   = 4
  disks    = [sakuracloud_disk.disk.id]
  cdrom_id = data.sakuracloud_cdrom.k3os.id

  # VNC経由でOSのインストール処理
  provisioner vnc {
    host      = self.vnc_host
    port      = self.vnc_port
    password  = self.vnc_password
    boot_wait = "40s"

    inline = [
      "rancher<enter>",
      "<wait5>",

      "sudo os-config<enter>",
      "<wait5>",

      "<enter>",
      "<wait5>",

      "<enter>",
      "<wait5>",

      "y<enter>",
      "<wait5>",

      "${var.github_username}<enter>", # terraformの$記法も使えるしコメントも書ける
      "<wait10s>",

      "<enter>",
      "<wait5>",

      "<enter>",
      "<wait5>",

      "<enter>",
      "<wait10s>",

      "y<enter>",
      "<wait5>",
    ]
  }

  # VNCでプロビジョニング後はSSHで接続してプロビジョニング
  connection {
    type        = "ssh"
    host        = self.ipaddress
    user        = "rancher"
    private_key = file("/Users/kaz/.ssh/id_rsa")
    script_path = "/home/rancher/bootstrap.sh"
  }
  provisioner "remote-exec" {
    inline = [
      "echo ${self.ipaddress} `hostname` | sudo tee -a /etc/hosts", # /etc/hostsに自身のホスト名を追記
      "kubectl cluster-info",
      "kubectl get cs",
      "kubectl version",
    ]
  }
}

終わりに

使い所は限られますが、Packerを使うまでもないちょっとしたコマンドを実行しておきたいケースに便利だと思います。

なお、TerraformでのCustom Provisionerの仕組みはこちらにコメントされているように将来的に大きく変わる可能性もある点は注意しておいてください。
(利用する側としてはあまり関係ないかもしれませんが)

github.com

以上です。

Rioにビルトインされているサービスを確認する - GrafanaとKiali

f:id:febc_yamamoto:20190603214650p:plain

前回RioでGitリポジトリを指定してのRunを試してみました。

febc-yamamoto.hatenablog.jp

rio runの引数としてGitリポジトリを指定するだけでリポジトリの変更検知〜イメージのビルド〜公開という仕組みが利用できました。

今回はそれらを実現している、Rioにビルドインされているサービスを確認していきます。

なお、今回はこちらのTerraformモジュールを使ってさくらのクラウド+RKEクラスタ上にRioをインストールしました。

github.com

Rioでどのようなサービスが動いているか確認する。

RioKubernetes上にデプロイされるため、kubectlなどでワークロードを確認(例えばkubectl get pod -n rio-systemを実行)することでどのようなサービスが動いているか調べることができます。
が、より簡単に確認できるコマンドrio --system psが提供されていますので今回はこちらを利用します。 (なお、この--systemオプションは省略形(-s)も利用可能です。またrio logsなどにも対応しています。)

ということで早速確認してみます。

rio --system psの実行

実行結果は以下の通りでした。

$ rio --system ps
NAME                          CREATED         ENDPOINT                                           REVISIONS   SCALE     WEIGHT    DETAIL
rio-system/autoscaler         3 minutes ago                                                      v0          1         100%      
rio-system/build-controller   2 minutes ago                                                      v0          1         100%      
rio-system/buildkit           3 minutes ago                                                      v0          1         100%      
rio-system/cert-manager       2 minutes ago                                                      v0          1         100%      
rio-system/grafana            2 minutes ago   https://grafana-rio-system.6y9oo6.on-rio.io:9443   v0          1         100%      
rio-system/istio-citadel      2 minutes ago                                                      v0          1         100%      
rio-system/istio-gateway      2 minutes ago                                                      v0          1         100%      
rio-system/istio-pilot        2 minutes ago                                                      v0          1         100%      
rio-system/istio-telemetry    2 minutes ago                                                      v0          1         100%      
rio-system/kiali              2 minutes ago   https://kiali-rio-system.6y9oo6.on-rio.io:9443     v0          1         100%      
rio-system/prometheus         3 minutes ago                                                      v0          1         100%      
rio-system/registry           2 minutes ago   https://registry-rio-system.6y9oo6.on-rio.io:9443  v0          1         100%      
rio-system/webhook            2 minutes ago   https://webhook-rio-system.6y9oo6.on-rio.io:9443   v0          1         100% 

ENDPOINTにURLが表示されているものがありますね。

Grafana

まずは試しにgrafanaのENDPOINTをブラウザで開いてみます。

お、grafanaのダッシュボードが表示されましたね。

f:id:febc_yamamoto:20190603212655p:plain

Istio関連のダッシュボードがありますね。

f:id:febc_yamamoto:20190603212907p:plain

いい感じです。

f:id:febc_yamamoto:20190603212931p:plain

f:id:febc_yamamoto:20190603213032p:plain

kiali

kialiはIstio向けの可視化ツールです。

www.kiali.io

これもENDPOINTをブラウザで開いてみます。

f:id:febc_yamamoto:20190603213112p:plain

これも表示できましたね。早速ログインしてみましょう。 ユーザー名/パスワードともadminでログインできます。

f:id:febc_yamamoto:20190603213146p:plain

f:id:febc_yamamoto:20190603213323p:plain

f:id:febc_yamamoto:20190603213354p:plain

いい感じですね。

その他のENDPOINT

他にはイメージレジストリとWebhookにENDPOINTが表示されてますね。 レジストリは試したらイメージのpushなどもできました。

# 適当なイメージをビルド
$ docker image build -t registry-rio-system.6y9oo6.on-rio.io:9443/test .
# pushしてみる
$ docker push registry-rio-system.6y9oo6.on-rio.io:9443/test

Webhookは試そうとしたのですが疲れちゃったのでまた今度。

終わりに

今回はここまで。以上です。

sakura-cloud-controller-managerのデプロイ&トラブルシューティング

さくらのクラウドKubernetesを利用する場合、マネージドなLBなどを扱うためのcloud-controller-manager実装として sakura-cloud-controller-managerというものがあります。

febc-yamamoto.hatenablog.jp

こちらのデプロイでよく引っかかる点やトラブルシューティング方法についてメモを発掘したので整理がてら残しておきます。

さくらのクラウド上のリソースの構成

Kubernetesクラスタで利用するさくらのクラウド上のリソースの構成は以下の点に注意が必要です。

  • ネットワーク構成
  • スイッチ(ルータ)への@k8sタグ設定

ネットワーク構成

sakura-cloud-controller-managerはサーバをスイッチ+ルータ or スイッチに接続する必要があります。
共有セグメント(共有回線)はサポートしていませんので注意してください。

スイッチ(ルータ)への@k8sタグ設定

sakura-cloud-controller-managerはさくらのクラウドAPIで取得したスイッチ(ルータ)の情報を元にどのスイッチを利用するかを決定します。
その際に@k8sタグの付けられたリソースかで判定を行なっています。

タグの付け忘れにご注意ください。

クラスタの構成

次にKubernetesクラスタの構成についてですが、以下2点に注意する必要があります。

順番にみていきます。

--cloud-providerオプションを適切に指定すること

Kubernetesでexternalなcloud-controller-managerを起動するには--cloud-providerオプションを適切に指定する必要があります。

参考: Kubernetes Cloud Controller Manager - Kubernetes

具体的には以下2点です。

  • kubeletのパラメータとして--cloud-provider=externalを指定しておくこと
  • kube-apiserverとkube-controller-managerには--cloud-providerを指定しないこと

kubeadmを利用してクラスタのデプロイを行う場合は/var/lib/kubelet/kubeadm-flags.envファイルや/etc/default/kubeletファイルなどで適切に指定しましょう。

参考: Installing kubeadm - Kubernetes

Nodeに付与されるtaintsへの対応

先ほどの参考ドキュメントに書いてありますが、kubeletに--cloud-provider=externalを指定するとノードに対し以下のようなtaintsが設定されます。

- apiVersion: v1
  kind: Node
  spec:
    taints:
    - effect: NoSchedule
      key: node.cloudprovider.kubernetes.io/uninitialized
      value: "true"

sakura-cloud-controller-managerをデプロイする際はこのtaintsに対するtolerationsが適切に設定されている必要があります。

# tolerationsの設定例
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sakura-cloud-controller-manager
  namespace: kube-system
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: sakura-cloud-controller-manager
        image: "sacloud/sakura-cloud-controller-manager:0.3.0"
      tolerations:
        # tolerationsを設定しておく
        - key: node.cloudprovider.kubernetes.io/uninitialized
          value: "true"
          effect: NoSchedule

もしkubeletに--cloud-providerを指定しなかったらどうなるの?

この場合でもsakura-cloud-controller-managerのデプロイ自体はうまくいくように見えます。
が、ノードのExternalIPが適切に設定されないため、type: LoadBalancerなサービスを作ってもロードバランサが作成されるものの実サーバが登録されないという状態になります。

serviceは作成されるけど、、、

f:id:febc_yamamoto:20190603165301p:plain

実サーバは登録されていない

f:id:febc_yamamoto:20190603165314p:plain

クラスタ内のノード名とさくらのクラウド上でのサーバ名が一致していること

これはsakura-cloud-controller-manager側の制約です。
sakura-cloud-controller-managerではノード名を条件にさくらのクラウドAPIで対象サーバの情報を取得しています。
このため、ノード名とサーバ名が異なるとうまくいきません。

kubeletの--hostname-overrideオプションなどで適切に設定しましょう。 (kubeadmの場合は--node-nameフラグが利用できます。 参考: kubeadm init - Kubernetes)

もしノード名とサーバ名が違うとどうなるの?

sakura-cloud-controller-managerでのノード情報の取得が行えず、ノードのtaintsが残り続けます。
このためPodを起動しようとしてもPendingのままとなります。
(まずkube-dnsなどの主要コンポーネントについてもPendingのままとなっているはずです。)

# Podを起動しようとしてもPendingのまま
$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7cdbd8cdc9-8g8mw   0/1     Pending   0          3m1s

# 主要コンポーネントもPendingのまま
$ kubectl get pod -n kube-system --selector k8s-app=kube-dns
NAME                        READY   STATUS    RESTARTS   AGE
kube-dns-58bd5b8dd7-swrlg   0/3     Pending   0          7m39s

さらにこの状態だとノードのInternalIP/ExternalIPが設定されず、kubectl logsを実行するとError from server: no preferred addresses found; known addresses: []などというエラーになります。

sakura-cloud-controller-managerのデプロイ

DeploymentまたはDaemonSetとしてデプロイしてください。
(現在Helmでデプロイする場合はDaemonSetのみサポートしています)

Deploymentとする場合のマニフェスト例は以下の通りです。 (この例はRKEを使ってデプロイしたKubernetesクラスタを利用しています。tolerationsやnodeSelectorsは環境に応じて適切に指定してください)

apiVersion: v1
kind: Secret
metadata:
  name: sakuracloud-api-keys
  namespace: kube-system
type: Opaque
data:
  access-token: "<APIアクセストークン>"
  access-token-secret: "<APIアクセスシークレット>"
---
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sakura-cloud-controller-manager
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  name: system:sakura-cloud-controller-manager
rules:
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
      - update
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - '*'
  - apiGroups:
      - ""
    resources:
      - nodes/status
    verbs:
      - patch
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - list
      - patch
      - update
      - watch
  - apiGroups:
      - ""
    resources:
      - services/status
    verbs:
      - list
      - patch
      - update
      - watch
  - apiGroups:
      - ""
    resources:
      - serviceaccounts
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - persistentvolumes
    verbs:
      - get
      - list
      - update
      - watch
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - create
      - get
      - list
      - watch
      - update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: system:sakura-cloud-controller-manager
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:sakura-cloud-controller-manager
subjects:
  - kind: ServiceAccount
    name: sakura-cloud-controller-manager
    namespace: kube-system
---

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    k8s-app: cloud-controller-manager
  name: sakura-cloud-controller-manager
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: cloud-controller-manager
  template:
    metadata:
      labels:
        k8s-app: cloud-controller-manager
    spec:
      dnsPolicy: Default
      hostNetwork: true
      serviceAccountName: sakura-cloud-controller-manager
      containers:
      - name: sakura-cloud-controller-manager
        image: "sacloud/sakura-cloud-controller-manager:0.3.0"
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 256m
            memory: 256Mi
        command:
          - /usr/local/bin/sakura-cloud-controller-manager
          - --cloud-provider=sakuracloud
          - --allocate-node-cidrs=false
          - --configure-cloud-routes=false
        env:
          - name: SAKURACLOUD_ACCESS_TOKEN
            valueFrom:
              secretKeyRef:
                name: sakuracloud-api-keys
                key: access-token
          - name: SAKURACLOUD_ACCESS_TOKEN_SECRET
            valueFrom:
              secretKeyRef:
                name: sakuracloud-api-keys
                key: access-token-secret
          - name: SAKURACLOUD_ZONE
            value: "<対象ゾーン(is1a or is1b or tk1a)>"
          - name: SAKURACLOUD_CLUSTER_ID
            value: "default"
      tolerations:
        - key: node.cloudprovider.kubernetes.io/uninitialized
          value: "true"
          effect: NoSchedule

        - key: "CriticalAddonsOnly"
          operator: "Exists"
      nodeSelector:
        node-role.kubernetes.io/controlplane: "true"

実行時によくあるトラブル

よくあるトラブルとして、Serviceを作成してもいつまでもPendingのままというものがあります。

この場合、sakura-cloud-controller-managerのログを参照すると何かヒントが得られることがあります。

# ログの参照例
$ kubectl logs -f -n kube-system sakura-cloud-controller-managerのpod名

私も引っかかったことがあるのは次のようなエラーメッセージが出るケースです。

Improper request. The parameters of the specified error or input rule violation. Please check your entries.\n実サーバのIPアドレス(xxx.xxx.xxx.xxx)は同一ネットワークである必要があります

このケースは@k8sタグのついたスイッチ+ルータが複数存在している場合に発生します。
複数のスイッチ+ルータを使い分けたい場合はそれぞれのスイッチに対し識別用のタグを付与した上でk8s.usacloud.jp/router-selectorアノテーションをServiceに対して指定するようにすればOKです。

おまけ: RKEでデプロイするTerraformモジュール

こちらにTerraformのさくらのクラウドプロバイダー+RKEプロバイダーでクラスタ構築やsakura-cloud-controller-managerのデプロイまで行うモジュールを置いておきます。

github.com

以上です。

k3OSのISOイメージがさくらのクラウドに追加された

f:id:febc_yamamoto:20190529153900p:plain

さくらのクラウドパブリックISOイメージk3OSが追加されましたね!

cloud-news.sakura.ad.jp

前回の記事ではk3OSのISOイメージをダウンロード&アップロードして試しました。

febc-yamamoto.hatenablog.jp

これが本日パブリックISOイメージが追加されたことでISOイメージのダウンロード/アップロードが不要となりより簡単にk3OSを試せるようになりました。

パブリックISOイメージから起動してみる(ディスクレス)

前回はCLIを利用した方法を紹介しましたので、今回はコントロールパネルからの操作を紹介します。
前回はディスクにインストールする手順でしたが、今回はディスクレスでISOイメージから起動してそのまま使う方法にしてみました。 (ディスクを作成しない分お安くなっています)

コントロールパネルからサーバの追加

まずコントロールパネルからサーバ追加画面へ移動します。

サーバ追加画面ではシンプルモードのチェックを外すのがポイントです。
(サーバ作成時にISOイメージを挿入するには非シンプルモードである必要があります)

その後、ディスクレスを選択した上で挿入するISOイメージとしてk3OS v0.2.0 amd64を選択します。

f:id:febc_yamamoto:20190529115651p:plain

もしディスクを接続したい場合は新規ディスクを作成を選択し、ディスクソースにブランクを指定すればISOイメージ選択欄が出てきます。

次のNICの設定はデフォルトのインターネットに接続を選択しておきます。

f:id:febc_yamamoto:20190529120303p:plain

これは共有セグメント(共有回線)経由でインターネット接続を行うという指定です。 共有セグメントではDHCPによるグローバルIPの割り当てが利用可能となっています。

manual.sakura.ad.jp

なお、k3OSのドキュメントによると、サーバのコア数/メモリはデフォルトの1コア/1GBで大丈夫なようです。

Live install (boot from ISO) requires at least 1GB of RAM.

GitHub - rancher/k3os: Purpose built OS for Kubernetes, fully managed by Kubernetes.

しかし1GBメモリだとイメージのpullなどでエラーになる場合があったため手元の環境ではメモリ2GBで試しました。 (他の要因もあるかもしれませんが調べてません)

参考: CLIでサーバ作成する場合

CLIの場合は以下のコマンドでOKです。

$ usacloud server build --name k3os --disk-mode diskless --iso-image-id `usacloud iso-image read -q k3OS` --memory 2

起動してコンソール接続

サーバを作成&起動後、コントロールパネルからコンソールを開きます。

f:id:febc_yamamoto:20190529120551p:plain

後は前回と同じくユーザー名rancher(パスワードなし)でログイン可能です。

ログイン後の設定

SSH

このまま試しても良いのですが、コンソール接続だとコピペができない(ペーストはコンパネ経由でできる)など色々不便なのでSSH接続できるようにします。

今回はGitHubからユーザーの公開鍵を取得して利用します。
以下のコマンドをコンソールで実行します。ユーザー名部分は任意のものに変更してください。

# GitHubのユーザー名が"yamamoto-febc"の場合
$ curl -L -o /home/rancher/.ssh/authorized_keys https://github.com/yamamoto-febc.keys
$ chmod 0600 /home/rancher/.ssh/authorized_keys

なお、コンソール接続している時点ではキーボードがUS配列になっていると思います。 普段US配列以外をお使いの場合は記号の入力の際にご注意ください。

この後SSHで接続できるようになっているはずです。

$ ssh rancher@サーバのグローバルIPアドレス

なお公開鍵を用意するのが面倒な方は以下のコマンドでrancherユーザーにパスワードを設定する方法もあります。

$ sudo passwd rancher

/etc/hosts

/etc/hostsを編集し自身のホスト名を解決できるようにします。 ホスト名はhostnameコマンドなどで、IPアドレスip a show dev eth0コマンドなどで確認しておきます。

今回は/etc/hostsを以下のようにしました。

127.0.0.1       localhost localhost.localdomain
127.0.1.1       k3os-14500 k3os-14500.localdomain

::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

# これを追記(IPアドレス/ホスト名は適宜修正)
153.120.168.44 sv-113100883194

これでひとまず利用できる環境が整いました。 後は色々動かしてみましょう。

終わりに

ISOイメージを用意してくれてるのは楽ですね。 とりあえず雰囲気を確認したいというような場合に手軽に使えて良いと思います。

以上です。

RioでGitHubリポジトリからのRunを試す

新しくなったRio

f:id:febc_yamamoto:20190525222428p:plain

KubeCon EUにあわせ新しくなったRioがアナウンスされました。

Rio自体は以前からOSSとして公開されていましたが、今回はRancher Labsからのリリースアナウンスもありある程度開発に区切りがついたということなのでしょう。

rancher.com

公式サイトもできてました。

rio.io

今回は新しくなったRioを軽く触ってみました。

なおRioについては以前ブログ書いてますのでこちらもご一読ください。

febc-yamamoto.hatenablog.jp

事前準備

Kubernetesクラスタ

以前のRioスタンドアロンという、embeddedなk8sクラスタ(後のk3s)を起動してくれるモードがありましたが、 新しいRioではk8sクラスタはあらかじめ準備しておく必要があります。

お好みのクラスタを用意しておきましょう。

k3sやDocker for Mac(のk8s)、minikubeもOKですし、GKE/AKS/EKSももちろんOKとのことです。 なおk8s v1.13以降が必要とのことですのでバージョンには注意しておいてください。

rioコマンドのインストール

フロントエンドとなるコマンドrioをインストールしておきます。

以下のコマンドでOKです。

$ curl -sfL https://get.rio.io | sh - 

上記コマンドで実行されるシェルスクリプトの確認がめんどくさい場合はGitHubのReleaseページから直接バイナリをダウンロードしてもOKです。

Releases · rancher/rio · GitHub

Rioのセットアップ

各種コンポーネントのデプロイ

まずはRioの各種コンポーネントk8sクラスタ上にデプロイします。 rio installコマンドを実行することでrio-systemというnamespaceにデプロイされます。

$ rio install

Creating namespace rio-system
Defaulting cluster CIDR to 10.43.0.1/16
Deploying Rio control plane....
Waiting for rio controller to initialize
Waiting for rio controller to initialize
Waiting for rio controller to initialize
Waiting for rio controller to initialize
Waiting for rio controller to initialize
Waiting for rio controller to initialize
Waiting for rio controller to initialize
Waiting for all the system components to be up. Not ready component: [autoscaler build-controller buildkit cert-manager grafana istio-citadel istio-pilot istio-telemetry kiali prometheus registry webhook]
Waiting for all the system components to be up. Not ready component: [autoscaler build-controller buildkit cert-manager grafana istio-citadel istio-pilot istio-telemetry kiali prometheus registry webhook]
Waiting for all the system components to be up. Not ready component: [autoscaler buildkit cert-manager grafana istio-citadel istio-pilot istio-telemetry kiali webhook]
Waiting for all the system components to be up. Not ready component: [cert-manager grafana istio-pilot kiali]
Waiting for service loadbalancer to be up
Waiting for service loadbalancer to be up
Waiting for service loadbalancer to be up
Waiting for service loadbalancer to be up
rio controller version v0.1.1-rc3 (2771703b) installed into namespace rio-system
Please make sure all the system pods are actually running. Run `kubectl get po -n rio-system` to get more detail.
Controller logs are available from `rio systemlogs`

Welcome to Rio!

Run `rio run https://github.com/rancher/rio-demo` as an example

なお、ここでtype=LoadBalancerなserviceが使えない場合はこのまま待つかHostPortsを使うか聞かれます。
コードを見るとそのほかにも分岐するパターンが結構あるようですね。
環境に合わせ適宜回答しましょう。

デプロイされたか確認

rio installが完了したらkubectl get pod -n rio-systemで確認してみます。

$ kubectl get pod -n rio-system

NAME                               READY   STATUS    RESTARTS   AGE
autoscaler-696866b8f7-9fbcq        1/1     Running   0          9m11s
build-controller-94b48b58f-m5kgw   2/2     Running   0          8m38s
buildkit-7f46884f98-qkjv9          2/2     Running   0          9m6s
cert-manager-57b4f876c5-hnxq6      1/1     Running   0          8m43s
grafana-68c6d8494d-rt9gg           2/2     Running   0          9m6s
istio-citadel-d76685f74-kczqs      1/1     Running   0          9m3s
istio-gateway-4ssl5                2/2     Running   0          8m57s
istio-gateway-872gl                2/2     Running   0          8m57s
istio-gateway-979xb                2/2     Running   0          8m57s
istio-pilot-85b894cd75-gg4n5       2/2     Running   0          8m54s
istio-telemetry-6b686cbbf5-bzwq5   2/2     Running   0          9m7s
kiali-7d4b6dc78c-mm5r2             2/2     Running   0          8m46s
prometheus-59c5655d9c-rw8mp        1/1     Running   0          9m11s
registry-6c4fddb8b6-2bvrm          2/2     Running   0          8m40s
registry-proxy-46ftw               1/1     Running   0          9m8s
registry-proxy-shjf9               1/1     Running   0          9m8s
registry-proxy-smszz               1/1     Running   0          9m8s
rio-controller-86c4bf9698-6blb5    1/1     Running   0          9m27s
webhook-5f6fc8f6f9-q6h6w           2/2     Running   0          8m28s

AutoScalerやKnativeのbuild-controller、buildkit、cert-manager、grafana、istio、kiali、prometheus、、、、と色々なPodが並んでますね。

Rioの新機能を試す - GitリポジトリからのRun

前回Rioを試した時にはなかった機能としてGitHubリポジトリのURLを指定してRunする機能が入っていました。
RioはデフォルトでKnative+buildkitでイメージをビルドするようになっているようです。
(オプションでbuildpackなども使えるようですがまだ試してないです)

ということで早速試してみました。

確認にはこちらのリポジトリを用意しました。

github.com

これはgolangでHTTPサーバを立ち上げHello Rio!というメッセージを返すだけの簡単なもので、main.goDockerfileの2ファイルだけで構成されているリポジトリとなっています。

main.goは以下の通りです。

package main


import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func handler(w http.ResponseWriter, r *http.Request) {
    time.Sleep(100 * time.Millisecond)
    fmt.Fprintln(w, "Hello Rio!!")
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Dockerfileは以下の通りです。

FROM golang:1.11.1
ENV GOPATH="/go"
RUN ["mkdir", "-p", "/go/src/github.com/yamamoto-febc/rio-demo"]
COPY * /go/src/github.com/yamamoto-febc/rio-demo/
WORKDIR /go/src/github.com/yamamoto-febc/rio-demo
RUN ["go", "build", "-o", "demo"]
CMD ["./demo"]

Runしてみる

ということで早速Runしてみました。

# -pオプション: 公開するポート -nオプション: 名前(今回はbuildという名前)
$ rio run -p 8080/http -n build https://github.com/yamamoto-febc/rio-demo

rio psを実行してみるとイメージのビルドを待機中と出ました。

$ rio ps

# イメージのビルド待ち
NAME            CREATED         ENDPOINT                                      REVISIONS   SCALE     WEIGHT    DETAIL
default/build   2 seconds ago   https://build-default.7vlxuv.on-rio.io:9443   v0          0/1       100%      v0 NotReady; v0 waiting on build

しばらく待つとイメージがビルドされ、コンテナの起動~公開まで行われました。

# イメージがビルドされ、コンテナ起動~公開までされた状態
$ rio ps
NAME            CREATED         ENDPOINT                                      REVISIONS   SCALE     WEIGHT    DETAIL
default/build   6 minutes ago   https://build-default.7vlxuv.on-rio.io:9443   v0          1         100%      

# エンドポイント宛てにリクエストしてみる
$ curl https://build-default.7vlxuv.on-rio.io:9443

Hello Rio!!

無事表示されましたね!!!

コードの修正〜pushを試す

次にコードを修正し、コミットとプッシュを試してみます。 Rioはデフォルトではmasterブランチへのタグ/コミットのpushをポーリングで検知してくれるとのことです。 (オプションでWebhookも利用できるとのこと)

ということで先ほどのコードを修正しpushして反映されるか確認してみます。

# メッセージを書き換えてみる: "Hello Rio!" から"Bonjour Rio!"へ
$ vi main.go

$ コミットしてpush
$ git add main.go
$ git commit -m"Update messages"
$ git push origin master

pushしたらRioが検知しているかrio revisionコマンドで確認してみます。

$ rio revision default/build
NAME                   IMAGE                                                                   CREATED          SCALE     ENDPOINT                                             WEIGHT    DETAIL
default/build:v0       localhost:5442/default/build:ff1d967decbfd34ecd1268d72b5a4e8e1067f9ee   19 minutes ago   1         https://build-v0-default.7vlxuv.on-rio.io:9443       100       
default/build:v37d26                                                                           28 seconds ago   0/1       https://build-v37d26-default.7vlxuv.on-rio.io:9443   0         

default/build:v37d26というリビジョンができてますね。こちらをビルド中のようです。 しばらく待つと次のようになりました。

rio revision default/build
NAME                   IMAGE                                                                                 CREATED          SCALE     ENDPOINT                                             WEIGHT    DETAIL
default/build:v0       localhost:5442/default/build:ff1d967decbfd34ecd1268d72b5a4e8e1067f9ee                 21 minutes ago   1         https://build-v0-default.7vlxuv.on-rio.io:9443       0         
default/build:v37d26   localhost:5442/default/build-169daa9-b92af:37d2632cb0d649dda800f5375b48b19419836f93   2 minutes ago    1         https://build-v37d26-default.7vlxuv.on-rio.io:9443   100       

新しい方のリビジョンのビルドが終わり、そちらに切り替わっているようですね。WEIGHTが新しいリビジョンの方に100%となりました。
では先ほどのエンドポイントあてに再度リクエストしてみます。

# 再度エンドポイント宛てにリクエストしてみる
$ curl https://build-default.7vlxuv.on-rio.io:9443

Bonjour Rio!!

お!!!!更新されてますね!

この手軽さはなかなか良いですね!

ということで

時間と体力切れのため今回はここまでです。 とっつきにくいKubernetesのあれこれを上手く覆い隠すフロントエンド、という印象を持ちました。

モニタリング面なども面白そうなので時間を見つけてもう少し触ってみたいところです。

以上です。

k3os on さくらのクラウド

f:id:febc_yamamoto:20190520181231p:plain

k3osを試してみましたのでメモ残しておきます。

Update: 2019/5/29

さくらのクラウド側でk3OSのISOイメージが提供されるようになりました。
この記事でのISOイメージのダウンロード/アップロードの部分は不要となりました。

febc-yamamoto.hatenablog.jp

===Updateここまで

k3osって?

kubernetesに特化した軽量OSで、軽量kubernetesディストリビュージョンである「k3s」を組み込んだOSとなっています。

k3os.io

Public Keyでも取り上げられましたね。 www.publickey1.jp

前見たときはリリースページにARM向けのISOファイルしかなかったのですが、今日改めて確認したらAMD64向けもちゃんと提供されるようになってたので試してみました。

試してみる

インストール

流れとしては以下の通りです。

  • k3osのリリースページからISOファイル(AMD64向け)をダウンロード
  • ISOファイルを使って仮想マシンなどを起動
  • 起動したらos-configコマンドでディスクへインストール

今回はさくらのクラウド上に仮想マシンを作成して試します。

ISOファイルのダウンロード〜さくらのクラウドへアップロード

curlコマンドでダウンロード、usacloudコマンド(さくらのクラウド向けCLI)でISOファイルをアップロードします。

# ISOファイルのダウンロード
$ curl -LO https://github.com/rancher/k3os/releases/download/v0.2.0/k3os-amd64.iso

# アップロード
$ usacloud iso-image create --iso-file k3os-amd64.iso --name k3os

サーバ作成〜ISOイメージの挿入〜起動

以下のコマンドでサーバ作成〜ISOイメージを挿入〜起動まで行います。

$ usacloud server build --name k3os \
                        --core 2 \
                        --memory 4 \
                        --disk-size 40 \
                        --iso-image-id `usacloud iso-image read -q k3os`

VNCで接続

サーバが起動したらコンパネからコンソールを開くかVNC接続して操作できるようにします。 今回は以下のコマンドでVNC接続を行います。(windowsの方は拡張子.vncが適切に関連付けされていれば動くはずです)

$ usacloud server vnc --wait-for-boot k3os

うまくいけばこんな感じの画面が表示されるはずです。牛ですね!

f:id:febc_yamamoto:20190520173013p:plain

ユーザー名rancherでパスワードなしでログインできるはずです。

ディスクへのインストール

ディスクへのインストールは表示されている通りsudo os-configコマンドを実行すればOKです。
いくつか聞かれますが適当に進めていきます。

途中でSSH用の公開鍵をGitHubから取ってくるか聞いてくれます。この機能便利ですよね。

f:id:febc_yamamoto:20190520174400p:plain

あとはインストールが進みます。インストールが終わると再起動されるはずです。

SSHで接続してみる

ディスクへのインストール&再起動まで終わったらSSHで接続してみます。

$ ssh rancher@<サーバのIPアドレス>

動作確認

とりあえずkubectlしてみました。 f:id:febc_yamamoto:20190520175745p:plain

OKっぽいですね。

次にpodを起動してみます。

kubectl run nginx --image=nginx

、、、あれ?いつまでたっても上がってこない? ということでログなどを確認すると、どうも自身のホスト名が名前解決できないってエラーが出てるようでした。

Waiting for hostname sv-NNNNNNNNNNNN to be resolvable: lookup sv-NNNNNNNNNNNN on 133.242.0.3:53: no such host

このIssueですかね。 cannot run k3s server · Issue #60 · rancher/k3s · GitHub

ひとまず/etc/hostsを編集したら動きました。

f:id:febc_yamamoto:20190520180519p:plain

時間なくなっちゃったので細かな検証はまた今度。

終わりに

k3sの環境構築が楽に行えるのが確認できました。 細かいところはまだ確認できてませんが追々試していきます。

2019/5/20 追記

Packerでk3osのアーカイブ(AWSでのAMIみたいなやつ)作るサンプルを作成しておきました。

github.com

毎回ISOイメージからインストールするのが面倒という場合などにどうぞ。

===追記ここまで

以上です。