2019年6月3日月曜日

Ruby から k8s を操作してみた

概要

Ruby で k8s の API をいろいろとコールしてみました
Pod の作成から削除、一覧の取得のサンプルを紹介します
また使用しているライブラリは k8s-client になります

環境

  • macOS 10.14.5
  • minikube v0.28.2
  • Ruby 2.6.2p47
    • k8s-client 0.10.0

準備

  • bundle init
  • vim Gemfile
gem "k8s-client"
  • bundle install --path vendor

クライアント作成

まずは API をコールするためのクライアントを作成します

  • vim app.rb
require 'k8s-client'

client = K8s::Client.config(
  K8s::Config.load_file(
    File.expand_path '~/.kube/config'
  )
)
  • bundle exec ruby app.rb

k8s の config ファイルを指定するのが一番簡単だと思います
それ以外にもエンドポイントを指定する方法もあります

API とリソースの一覧表示

まずはテストとして k8s の API の一覧を取得してみます

  • vim app.rb
require 'k8s-client'

client = K8s::Client.config(
  K8s::Config.load_file(
    File.expand_path '~/.kube/config'
  )
)

client.apis.each { |a|
  puts a.api_version
  a.api_resources.each { |r|
    puts "  #{r.name}"
  }
}

api_version で API の一覧を取得しています
更に api_resources で API に紐づくリソースの一覧も取得しています
この API 名とリソースは API をコールする際に利用します

Pod 作成

基本は YAML で定義した情報はハッシュに落とし込めば OK です
あとはリクエストするパスとメソッドの組み合わせを指定します

  • vim app.rb
require 'k8s-client'

client = K8s::Client.config(
  K8s::Config.load_file(
    File.expand_path '~/.kube/config'
  )
)

deployment = K8s::Resource.new(
  apiVersion: 'extensions/v1beta1',
  kind: 'Deployment',
  metadata: {
    name: 'redis-single',
    namespace: 'default'
  },
  spec: {
    replicas: 1,
    template: {
      metadata: {
        labels: {
          app: 'redis',
          role: 'single'
        }
      },
      spec: {
        containers: [
          {
            name: 'single',
            image: 'redis',
            ports: [
              {
                containerPort: 6379
              }
            ]
          }
        ]
      }
    }
  }
)
ret = client.api('extensions/v1beta1').resource('deployments').create_resource(deployment)
pp ret.to_json

リソースを作成する場合は K8s::Resource.new を使います
これにハッシュを指定することでリクエスト用の JSON に内部的に変換してくれます
今回は redis コンテナを立ち上げる Deployment になっています
ポイントは metadata.namespace で namespace を指定する点です
default でも指定する必要があります
これを指定しないと POST /apis/extensions/v1beta1/deployments => HTTP 405 Method Not Allowed: the server does not allow this method on the requested resource (K8s::Error::MethodNotAllowed) となり本来アクセスするパスの /apis/extensions/v1beta1/namespaces/{namespace}/deployments にアクセスしてくれません
型の指定にも注意しましょう
レプリカの数やポート番号は整数で指定する必要があります
あとはちゃんとパラメータの階層や構造を間違えないようにしましょう
YAML からハッシュに落とし込む際に自分は階層がずれて K8s::Error::Invalid が発生しました

レスポンスは実際にリクエストした Deployment のリソース情報が返ってきます (長いので省略)
以下のように Deployment, ReplicaSet, Pod が作成できていれば OK です

  • kubectl get deploy,rs,po
NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/redis-single   1/1     1            1           54s

NAME                                            DESIRED   CURRENT   READY   AGE
replicaset.extensions/redis-single-864f5b6474   1         1         1       54s

NAME                                READY   STATUS    RESTARTS   AGE
pod/redis-single-864f5b6474-hwxxj   1/1     Running   0          54s

Pod 作成 (yaml を直接使う)

直接 YAML ファイルを使用する方法もあります
特に YAML に変更がない場合などはこっちの方法を使ったほうが簡単にできると思います

  • vim redis_deployment.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: redis-single
  namespace: default
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: redis
        role: single
    spec:
      containers:
      - name: single
        image: redis
        ports:
        - containerPort: 6379

YAML の場合も metadata.namespace を指定するようにしてください

  • vim app.rb
require 'k8s-client'

client = K8s::Client.config(
  K8s::Config.load_file(
    File.expand_path '~/.kube/config'
  )
)

resources = K8s::Resource.from_files('./redis_deployment.yml')
resources.each { |r|
  p client.create_resource(r)
}

これで先ほどと同じリソースが作成されます

Service 作成

作成した Pod を外部からアクセスできるようにしてみます

  • vim app.rb
require 'k8s-client'

client = K8s::Client.config(
  K8s::Config.load_file(
    File.expand_path '~/.kube/config'
  )
)

service = K8s::Resource.new(
  apiVersion: 'v1',
  kind: 'Service',
  metadata: {
    name: 'redis-single',
    namespace: 'default'
  },
  spec: {
    type: 'NodePort',
    selector: {
      role: 'single'
    },
    ports: [
      {
        protocol: 'TCP',
        port: 6379,
        targetPort: 6379
      }
    ]
  }
)
ret = client.api('v1').resource('services').create_resource(service)
pp ret.to_json

これも Deployment と同じで namespace を指定します
あとは YAML をそのままハッシュに変換すれば OK です
アクセスするパスは client.api('v1').resource('services') になります

  • kubectl get svc
NAME           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes     ClusterIP   10.96.0.1      <none>        443/TCP          122d
redis-single   NodePort    10.107.200.3   <none>        6379:32503/TCP   16s
  • redis-cli -h $(minikube ip) -p 32503 info server

作成したリソースの一覧取得

kubectl get をやってみます

  • vim app.rb
require 'k8s-client'

client = K8s::Client.config(
  K8s::Config.load_file(
    File.expand_path '~/.kube/config'
  )
)

pods = client.api('v1').resource('pods', namespace: 'default').list
pods.each { |p|
  p p.metadata.name
}

deployments = client.api('extensions/v1beta1').resource('deployments', namespace: 'default').list
deployments.each { |d|
  p d.metadata.name
}

replicasets = client.api('apps/v1').resource('replicasets', namespace: 'default').list
replicasets.each { |r|
  p r.metadata.name
}

services = client.api('v1').resource('services', namespace: 'default').list
services.each { |s|
  p s.metadata.name
}

各リソースごとに指定可能な API バージョンの組み合わせが決まっているので冒頭で紹介した API とリソースを表示するスクリプトなどで確認してください
とりあえず metadata.name だけ指定していますがそれ以外にも取得できます
K8s::Resource クラスのインスタンスになります

後処理

  • kubectl delete svc redis-single
  • kubectl delete deploy redis-single

Ruby からやる場合は以下の通りです

require 'k8s-client'

client = K8s::Client.config(
  K8s::Config.load_file(
    File.expand_path '~/.kube/config'
  )
)

p client.api('extensions/v1beta1').resource('deployments', namespace: 'default').delete('redis-single')
p client.api('apps/v1').resource('replicasets', namespace: 'default').delete('redis-single-57b6f5f7cb')
p client.api('v1').resource('pods', namespace: 'default').delete('redis-single-57b6f5f7cb-smx27')
p client.api('v1').resource('services', namespace: 'default').delete('redis-single')

ちゃんとすべてのリソースごとに削除する必要があります

最後に

k8s の API を Ruby から操作してみました
ライブラリの使い方にもなりますがハッシュでリソースを定義してリソースごとに割り当てられた API バージョンにリクエストする感じで使えます

API を直接読んでみると kubectl コマンドがどういうふうに実装されているかのイメージも付くと思います

参考サイト

0 件のコメント:

コメントを投稿