tjtjtjのメモ

自分のためのメモです

keycloak ユーザーストレージSPI

keycloak を試した時のメモ

ユーザー・ストレージSPI

keycloak-documentation.openstandia.jp

ユーザー・ストレージSPIを使用して、外部ユーザー・データベースとクレデンシャル・ストアに接続するように、Keycloakの拡張機能を実装できます。組み込みのLDAPActive Directoryのサポートは、このSPIを実際に実装したものです。設定などの作業をせず、すぐにKeycloakはローカル・データベースを使用して、ユーザーの作成、更新、検索とクレデンシャルの検証をします。 しかし、多くの場合、組織はKeycloakのデータモデルに移行できない外部の独自ユーザー・データベースをすでに持っています。このような状況では、アプリケーション開発者は、ユーザー・ストレージSPIの実装をコーディングして、外部ユーザーストアと、Keycloakがユーザーのログインおよび管理に使用する内部ユーザー・オブジェクト・モデルをブリッジすることができます。

ブリッジを書けば、独自ユーザーの認証ができるようになる。

dasniko/keycloak-user-spi-demo

github.com

これを利用させてもらう。 git clonemvn install で jar を作っておく。keycloak に jar をコピーして起動。

git clone https://github.com/dasniko/keycloak-user-spi-demo.git
cd keycloak-user-spi-demo
mvn install
docker create -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=password --name keycloak jboss/keycloak
docker cp ./target/keycloak-demo-user-spi.jar keycloak:/opt/jboss/keycloak/standalone/deployments/
docker start keycloak

keycloak に admin/password で入り、 user federation に demo-user-provider があるので選択しsave。

f:id:tjtjtjofthedead:20201024082041p:plain

すると users に keycloak-user-spi-demo のユーザーが表示される。 f:id:tjtjtjofthedead:20201024082123p:plain

ここ から username/password がわかる。

demo-user でログイン

あとは fred.flintstone/fred でkeycloakに入ったり、ロール付けたりするといいんじゃないですかね。

cuelang kubernetes チュートリアル#2

cuelang kubernetes チュートリアル#2

前回はディレクトリに散らばった yaml を cue にした。今回は cue をスリムにしていく。クイックダーティーコンバージョン

https://github.com/cuelang/cue/tree/master/doc/tutorial/kubernetes#quick-n-dirty-conversion

cue eval -c ./... > snapshot

ディレクトリ下の cue ファイルを評価し snapshot を得る。改変しても差異がないことを検証するため取っておく。

Usage:
  cue eval [flags]

Flags:
  -c, --concrete                 require the evaluation to be concrete
                                 評価を具体的にする必要がある

snapshot

service: {
    bartender: {
        apiVersion: "v1"
        kind:       "Service"
         metadata: {
            name: "bartender"
            labels: {
                component: "frontend"
                app:       "bartender"
                domain:    "prod"
            }
:

cp frontend/breaddispatcher/kube.cue .

テンプレートを作成するため deployment と service を含むファイルをコピーする。kube.cue をシンプル化していくのだが変更前の状態を確認しておく。

frontend/breaddispatcher/kube.cue

package kube

service: breaddispatcher: {
    apiVersion: "v1"
    kind:       "Service"
    metadata: {
        name: "breaddispatcher"
        labels: {
            app:       "breaddispatcher"
            component: "frontend"
            domain:    "prod"
        }
    }
    spec: {
        ports: [{
            port:       7080
            targetPort: 7080
            protocol:   "TCP"
            name:       "client"
        }]
        selector: {
            app:       "breaddispatcher"
            component: "frontend"
            domain:    "prod"
        }
    }
}
deployment: breaddispatcher: {
    apiVersion: "apps/v1"
    kind:       "Deployment"
    metadata: name: "breaddispatcher"
    spec: {
        replicas: 1
        template: {
            metadata: {
                labels: {
                    app:       "breaddispatcher"
                    component: "frontend"
                    domain:    "prod"
                }
                annotations: {
                    "prometheus.io.scrape": "true"
                    "prometheus.io.port":   "7080"
                }
            }
            spec: containers: [{
                name:  "breaddispatcher"
                image: "gcr.io/myproj/breaddispatcher:v0.3.24"
                ports: [{
                    containerPort: 7080
                }]
                args: [
                    "-etcd=etcd:2379",
                    "-event-server=events:7788",
                ]
            }]
        }
    }
}

次のように書き換える。kind:Service と kind:deployment のテンプレートができた。

package kube

service: [ID=_]: {
    apiVersion: "v1"
    kind:       "Service"
    metadata: {
        name: ID
        labels: {
            app:       ID    // by convention
            domain:    "prod"  // always the same in the given files
            component: string  // varies per directory
        }
    }
    spec: {
        // Any port has the following properties.
        ports: [...{
            port:       int
            protocol:   *"TCP" | "UDP"      // from the Kubernetes definition
            name:       string | *"client"
        }]
        selector: metadata.labels // we want those to be the same
    }
}

deployment: [ID=_]: {
    apiVersion: "apps/v1"
    kind:       "Deployment"
    metadata: name: ID
    spec: {
        // 1 is the default, but we allow any number
        replicas: *1 | int
        template: {
            metadata: labels: {
                app:       ID
                domain:    "prod"
                component: string
            }
            // we always have one namesake container
            spec: containers: [{ name: ID }]
        }
    }
}

この辺りを見ながら復習

cue eval ./... -c > snapshot2

再びディレクトリ下の cue ファイルを評価し snapshot2 を生成。が alert が出る。

$ cue eval ./... -c > snapshot2
/// .../kubernetes/tmp/services/mon/alertmanager
service.alertmanager.metadata.labels.component: incomplete value (string):
    ./kube.cue:11:24
service.alertmanager.spec.selector.component: incomplete value (string):
    ./kube.cue:11:24
deployment.alertmanager.spec.template.metadata.labels.component: incomplete value (string):
    ./kube.cue:36:28
:

kubernetes/tmp/services/mon/alertmanager/kube.cue を確認する。component がない。

package kube

service: alertmanager: {
    apiVersion: "v1"
    kind:       "Service"
    metadata: {
        annotations: {
            "prometheus.io/scrape": "true"
            "prometheus.io/path":   "/metrics"
        }
        labels: name: "alertmanager"
        name: "alertmanager"
    }
    spec: {
        selector: app: "alertmanager"
        // type: ClusterIP
        ports: [{
            name:       "main"
            protocol:   "TCP"
            port:       9093
            targetPort: 9093
        }]
    }
}

次にチュートリアルでは sed でゴニョゴニョする。

  • kube.cue を置換
    • component: string -> component: #Component
  • kube.cue の末尾に次を追加
    • #Component: string
  • frontend,infra ... の各ディレクトリにコンポ―メント名を指定したがファイルを生成
    • #Component: "frontend"
  • cue ファイルをtrim

diff をとると spec.selector の domain, component が揃っていることが分かる。

snapshot2 の mon alertmanager service

service: {
    alertmanager: {
        apiVersion: "v1"
        kind:       "Service"
        metadata: {
            name: "alertmanager"
            labels: {
                name:      "alertmanager"
                app:       "alertmanager"
                domain:    "prod"
                component: "mon"
            }
            annotations: {
                "prometheus.io/scrape": "true"
                "prometheus.io/path":   "/metrics"
            }
        }
        spec: {
            ports: [{
                name:       "main"
                port:       9093
                protocol:   "TCP"
                targetPort: 9093
            }]
            selector: {
                name:      "alertmanager"
                app:       "alertmanager"
                domain:    "prod"
                component: "mon"
            }
        }
    }
}

部分 eval を試す。

$ cue eval ./mon/alertmanager -e service
alertmanager: {
    apiVersion: "v1"
    kind:       "Service"
    metadata: {
        name: "alertmanager"
        labels: {
            name:      "alertmanager"
            app:       "alertmanager"
            domain:    "prod"
            component: "mon"
        }
        annotations: {
            "prometheus.io/scrape": "true"
            "prometheus.io/path":   "/metrics"
        }
    }
    spec: {
        ports: [{
            name:       "main"
            port:       9093
            protocol:   "TCP"
            targetPort: 9093
        }]
        selector: {
            name:      "alertmanager"
            app:       "alertmanager"
            domain:    "prod"
            component: "mon"
        }
    }
}

service.alertmanager を yaml で得る。

$ cue eval ./mon/alertmanager -e service.alertmanager --out yaml
apiVersion: v1
kind: Service
metadata:
  name: alertmanager
  labels:
    name: alertmanager
    app: alertmanager
    domain: prod
    component: mon
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/path: /metrics
spec:
  ports:
    - name: main
      port: 9093
      protocol: TCP
      targetPort: 9093
  selector:
    name: alertmanager
    app: alertmanager
    domain: prod
    component: mon

cue trim ./...

trim 作用はうまく説明できない。cue はこんなふうに矛盾しない限り合わせていくのだが、

trim したときどっちに寄るんだろうとか。いい感じにしてくれるということで。

trim の help を見る

$ cue help trim
trim removes fields from structs that can be inferred from constraints

A field, struct, or list is removed if it is implied by a constraint, such
as from an optional field maching a required field, a list type value,
a comprehension or any other implied content. It will modify the files in place.


Limitations

Removal is on a best effort basis. Some caveats:
- Fields in implied content may refer to fields within the struct in which
  they are included, but are only resolved on a best-effort basis.
- Disjunctions that contain structs in implied content cannot be used to
  remove fields.
- There is currently no verification step: manual verification is required.

Examples:

        $ cat <<EOF > foo.cue
        light: [string]: {
                room:          string
                brightnessOff: *0.0 | >=0 & <=100.0
                brightnessOn:  *100.0 | >=0 & <=100.0
        }

        light: ceiling50: {
                room:          "MasterBedroom"
                brightnessOff: 0.0    // this line
                brightnessOn:  100.0  // and this line will be removed
        }
        EOF

        $ cue trim foo.cue
        $ cat foo.cue
        light: [string]: {
                room:          string
                brightnessOff: *0.0 | >=0 & <=100.0
                brightnessOn:  *100.0 | >=0 & <=100.0
        }

        light: ceiling50: {
                room: "MasterBedroom"
        }

It is guaranteed that the resulting files give the same output as before the
removal.

トップレベルテンプレートの編集

ここからはチュートリアル見るべき。。。

  • kind 毎の共通構造と spec 構造をトップレベルテンプレートに抽出
  • port 定義簡略化の工夫
  • サブレベルテンプレート作成
  • cue trim --simplify Folding of Single-Field Structs https://cuelang.org/docs/tutorials/tour/intro/fold/
  • サブレベル毎のカスタマイズ

そうこうしているうちに

そうこうしているうちに v0.3.0-alpha3 がでている。

https://github.com/cuelang/cue/releases/tag/v0.3.0-alpha3

cuelang kubernetes チュートリアル#1

cuelang を触っている。まだ入門でたとは言えない感じだが、kubernetes チュートリアルが面白い。2周目で人に説明できるくらいになってきた。

https://github.com/cuelang/cue/tree/master/doc/tutorial/kubernetes

cue import オプション

cue import ./... -p kube -l 'strings.ToCamel(kind)' -l metadata.name -f -R

  -p, --package string           package name for non-CUE files
                                 非CUEファイルのパッケージ名
  -l, --path stringArray         CUE expression for single path component
                                 シングルパスコンポーネントのCUE式
  -f, --force                    force overwriting existing files
                                 ファイル上書き
  -R, --recursive                recursively parse string values
                                 文字列値を再帰的に解析する

cue import ./... -p kube -l 'strings.ToCamel(kind)' -l metadata.name -f で起きること

  1. ./... 下の yaml から cue を生成 kube.cue -> kube.yaml
  2. package kube を付与させる
  3. オブジェクトは -l 'strings.ToCamel(kind)' -l metadata.name 内に格納される オブジェクトを一意にする
  4. ファイル上書き

tree

$ tree . | head
.
└── services
    ├── frontend
    │   ├── bartender
    │   │   ├── kube.cue
    │   │   └── kube.yaml
    │   ├── breaddispatcher
    │   │   ├── kube.cue
    │   │   └── kube.yaml

services/frontend/bartender/kube.yaml

apiVersion: v1
kind: Service
metadata:
  name: bartender
:
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bartender
:

services/frontend/bartender/kube.cue

package kube

service: bartender: {
    apiVersion: "v1"
    kind:       "Service"
    metadata: {
        name: "bartender"
:
deployment: bartender: {
    apiVersion: "apps/v1"
    kind:       "Deployment"
    metadata: name: "bartender"
:

cat mon/prometheus/configmap.cue

yaml 内に文字列として yaml が埋め込まれている。cue にしてもまだ文字列としてのyamlのまま、これが気に入らないと。

mon/prometheus/configmap.cue

package kube

apiVersion: "v1"
kind:       "ConfigMap"
metadata: name: "prometheus"
data: {
    "alert.rules": """           <--- ここらへんが気に入らない
        groups:
        - name: rules.yaml         

cue import ./... -p kube -l 'strings.ToCamel(kind)' -l metadata.name -f -R で起きること

-Rオプションは、構成ファイルに埋め込まれた構造化されたYAMLまたはJSON文字列を検出し、これらを再帰的に変換しようとする

  • alert.rules の階層に _cue_alert_rules が出現
  • _cue_alert_rules の値は alert.rules の値を再帰的に解析したもの
  • アンダースコア(_)で始まるフィールドは、構成ファイルの出力時(exportとかevalかな)には含まれません
  • "alert.rules" の値は _cue_alert_rules の値をマーシャルしたもの
  • "encoding/yaml" を yaml656e63 としてインポート

mon/prometheus/configmap.cue

package kube

import yaml656e63 "encoding/yaml"                                     <--- yaml656e63

configMap: prometheus: {
    apiVersion: "v1"
    kind:       "ConfigMap"
    metadata: name: "prometheus"
    data: {
        "alert.rules": yaml656e63.Marshal(_cue_alert_rules)   <--- yaml656e63, _cue_alert_rules
        let _cue_alert_rules = {                              <--- _cue_alert_rules
            groups: [{
                name: "rules.yaml"
                rules: [{
                    alert: "InstanceDown"
                    expr:  "up == 0"
                    for:   "30s"
                    labels: severity: "page"
                    annotations: {
                        description: "{{$labels.app}} of job {{ $labels.job }} has been down for more than 30 seconds."

                        summary: "Instance {{$labels.app}} down"

cue eval ./mon/prometheus -e configMap.prometheus

./mon/prometheus 下のファイルのうち オブジェクト configMap.prometheus の内容について評価 次が得られる。eval で出てきたのは cue であって yaml じゃない

$ cue eval ./mon/prometheus -e configMap.prometheus
apiVersion: "v1"
kind:       "ConfigMap"
metadata: {
    name: "prometheus"
}
data: {
    "alert.rules": """       <--- cue内 yaml 文字列
        groups:
          - name: rules.yaml
            rules:
              - alert: InstanceDown
                expr: up == 0
                for: 30s
                labels:
                  severity: page
                annotations:
                  description: '{{$labels.app}} of job {{ $labels.job }} has been down for more than 30 seconds.'
                  summary: Instance {{$labels.app}} down

もとのyaml。完全一致でないが同等と言えるだろう。

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus
data:
  alert.rules: |-
    groups:
    - name: rules.yaml
      rules:
      - alert: InstanceDown
        expr: up == 0
        for: 30s
        labels:
          severity: page
        annotations:
          description: '{{$labels.app}} of job {{ $labels.job }} has been down for
            more than 30 seconds.'
          summary: Instance {{$labels.app}} down

yaml に export。yamlyaml のコメントは消えているが、おなじと言える。

$ cue export ./mon/prometheus -e configMap.prometheus --out yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus
data:
  alert.rules: |
    groups:
      - name: rules.yaml
        rules:
          - alert: InstanceDown
            expr: up == 0
            for: 30s
            labels:
              severity: page
            annotations:
              description: '{{$labels.app}} of job {{ $labels.job }} has been down for more than 30 seconds.'
              summary: Instance {{$labels.app}} down

cue から yaml を生成するときどうするの

yamlからcueを生成できるのは分かった。cueのチュートリアルやったのでcueはサイズを減らしたり、validationできるのは分かる。 cueからyaml生成は cue export --out yaml。 import でオブジェクトを一意にするため -l 'strings.ToCamel(kind)' -l metadata.name で2階層沈めた。 kind は型チェックに用いられ metaname は省略表記に用いられるのだろうなあ。 しかし、これでは取り出すとき -e configMap.prometheus 2階層(kind)(metaname)指定することになる。面倒です。どうすれば? チュートリアル進めればわかるんだけど。

cuelang を試す

在宅勤務で生活リズムが変化し、しばらく勉強時間が取れなくなった。 早起きし朝を勉強に充てている。今週は職場で教えてもらった cuelang を試している。

cuelang.org

github.com

今回できたのは著者リストと著作物リストの定義。このリストに惑わされ時間がかかった。 最終的なjsonをイメージしたところ、リストでなくオブジェクトだと気づいた。

authors_books

authors_books.cue

#Author: {
    id: string
    name: string
}
#Book: {
    id: string
    name: string
    author: #Author
}

authors: [ID=_]: {
    id: ID
    name: string
}
authors: kurt_vonnegut_jr: {
    name: "カート・ヴォネガット・ジュニア"
}
authors: arthur_charles_clarke: {
    name: "アーサー・チャールズ・クラーク"
}

books: [ID=_]: {
    id: ID
    name: string
    author: #Author
}
books: the_sirens_of_titan: {
    name: "タイタンの妖女"
    author: authors.kurt_vonnegut_jr
}
books: slaughterhouse_five: {
    name: "スローターハウス5"
    author: authors.kurt_vonnegut_jr
}
books: x2001_a_space_odyssey: {
    name: "2001年宇宙の旅"
    author: authors.arthur_charles_clarke
}
books: childhoods_end: {
    name: "幼年期の終り"
    author: authors.arthur_charles_clarke
}
$ cue export authors_books.cue
{
    "authors": {
        "kurt_vonnegut_jr": {
            "name": "カート・ヴォネガット・ジュニア",
            "id": "kurt_vonnegut_jr"
        },
        "arthur_charles_clarke": {
            "name": "アーサー・チャールズ・クラーク",
            "id": "arthur_charles_clarke"
        }
    },
    "books": {
        "the_sirens_of_titan": {
            "name": "タイタンの妖女",
            "id": "the_sirens_of_titan",
            "author": {
                "name": "カート・ヴォネガット・ジュニア",
                "id": "kurt_vonnegut_jr"
            }
        },
        "slaughterhouse_five": {
            "name": "スローターハウス5",
            "id": "slaughterhouse_five",
            "author": {
                "name": "カート・ヴォネガット・ジュニア",
                "id": "kurt_vonnegut_jr"
            }
        },
        "x2001_a_space_odyssey": {
            "name": "2001年宇宙の旅",
            "id": "x2001_a_space_odyssey",
            "author": {
                "name": "アーサー・チャールズ・クラーク",
                "id": "arthur_charles_clarke"
            }
        },
        "childhoods_end": {
            "name": "幼年期の終り",
            "id": "childhoods_end",
            "author": {
                "name": "アーサー・チャールズ・クラーク",
                "id": "arthur_charles_clarke"
            }
        }
    }
}

go-kit を試す #1

ここを参考に go-kit を試した。http で受けたメッセージを amqp に publish する。

tech.orylab.com

サービスのインタフェース定義

サービスはインタフェースで定義する

type PublishService interface {
    Publish(message string) (string, error)
}

サービスを実装

type publishService struct{}

func (publishService) Publish(s string) (string, error) {
    if s == "" {
        return "", errors.New("empty")
    }
    err := pub("testqueue", s)
    if err != nil {
        return "", err
    }
    return "Done", nil
}

func pub(qname string, message string) error {
    conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
    if err != nil {
        log.Print("failed Dial")
        return err
    }
    defer conn.Close()

    ch, err := conn.Channel()
    if err != nil {
        log.Print("failed Channel")
        return err
    }
    defer ch.Close()

    q, err := ch.QueueDeclare(
        qname, // name
        true,  // durable
        false, // delete when unused
        false, // exclusive
        false, // no-wait
        nil,   // arguments
    )
    if err != nil {
        log.Print("failed QueueDeclare")
        return err
    }

    err = ch.Publish(
        "",     // exchange
        q.Name, //q.Name, // routing key
        false,  // mandatory
        false,  // immediate
        amqp.Publishing{
            ContentType: "text/plain",
            Body:        []byte(message),
        })
    if err != nil {
        log.Print("failed Publish")
        return err
    }

    log.Printf("pulished %s : %s", qname, message)
    return nil
}

メッセージ関係

type publishRequest struct {
    S string `json:"s"`
}

type publishResponse struct {
    V   string `json:"v"`
    Err string `json:"err,omitempty"`
}

func decodePublishRequest(_ context.Context, r *http.Request) (interface{}, error) {
    var request publishRequest
    if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
        return nil, err
    }
    return request, nil
}

func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
    return json.NewEncoder(w).Encode(response)
}

エンドポイント

func makePublishEndpoint(svc PublishService) endpoint.Endpoint {
    return func(ctx context.Context, request interface{}) (interface{}, error) {
        req := request.(publishRequest)
        v, err := svc.Publish(req.S)
        if err != nil {
            return publishResponse{v, err.Error()}, nil
        }
        return publishResponse{v, ""}, nil
    }
}

main()

func main() {
    svc := publishService{}
    publishHandler := httptransport.NewServer(
        makePublishEndpoint(svc),
        decodePublishRequest,
        encodeResponse,
    )
    http.Handle("/publish", publishHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

curl

$ curl -s -X POST -d'{"s":"hello, world1"}' localhost:8080/publish
{"v":"Done"}

$ curl -s -X POST -d'{"s":"hello, world2"}' localhost:8080/publish
{"v":"Done"}

log

$go run main.go
2020/02/20 20:29:06 pulished testqueue : hello, world1
2020/02/20 20:29:08 pulished testqueue : hello, world2

ここらへん

後で試す

https://github.com/go-kit/kit/tree/master/transport/amqp

kong2.0 ハイブリッドモード

kong2.0 が出ていた。ポイントは次の3点。

ハイブリッドモード

https://docs.konghq.com/2.0.x/hybrid-mode/

  • コントロールプレーン/データプレーン分離
  • コントロールプレーン(cp)のkong
    • db アクセス有
    • KONG_ROLE=control_plane
  • データプレーン(dp)のkong
    • db アクセス無
    • db-less モードで cp から情報引っ張ってくる感じ?
    • KONG_ROLE=data_plane
  • cp/dpのセキュリティ
    • 証明書/キーペアが必要

証明書/キーペアを生成

だいぶ忘れている。。。雑にやってみる。。。

dbレスモードのkong 起動

$ docker run -d --name kong-less \
     -v "kong-vol:/usr/local/kong" \
     -e "KONG_DATABASE=off" \
     -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
     -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
     -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
     -p 8000:8000 \
     -p 8443:8443 \
     -p 8001:8001 \
     -p 8444:8444 \
     kong:2.0.0

証明書/キーペアを生成

$ docker exec -it kong-less sh
/ $ cd /usr/local/kong/
/usr/local/kong $ mkdir cert
/usr/local/kong $ cd cert
/usr/local/kong/cert $ kong hybrid gen_cert
Successfully generated certificate/key pairs, they have been written to: '/usr/local/kong/cert/cluster.crt' and '/usr/local/kong /cert/cluster.key'.
/usr/local/kong/cert $ ls -al
total 16
drwxr-xr-x    2 kong     nogroup       4096 Jan 23 11:31 .
drwxrwxr-x   14 kong     root          4096 Jan 23 11:31 ..
-rw-r--r--    1 kong     nogroup        526 Jan 23 11:31 cluster.crt
-rw-------    1 kong     nogroup        306 Jan 23 11:31 cluster.key
/usr/local/kong/cert $ exit

$ docker stop kong-less
$ docker rm kong-less

コントロールプレーンのkong起動

network 作って, cassandra 立ち上げて, マイグレーションして, kong 起動。kong-vol にはさっき作った証明書が入っている。

$ docker network create kong-net
$ docker run -d --name kong-database \
               --network=kong-net \
               -p 9042:9042 \
               cassandra:3

$ docker run --rm \
     --network=kong-net \
     -e "KONG_DATABASE=cassandra" \
     -e "KONG_PG_HOST=kong-database" \
     -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
     kong:2.0.0 kong migrations bootstrap
:
23 migrations processed
23 executed
Database is up-to-date

$ docker run -d --name kong-cp \
     --network=kong-net \
     -v "kong-vol:/usr/local/kong" \
     -e "KONG_ROLE=control_plane" \
     -e "KONG_CLUSTER_CERT=/usr/local/kong/cert/cluster.crt" \
     -e "KONG_CLUSTER_CERT_KEY=/usr/local/kong/cert/cluster.key" \
     -e "KONG_DATABASE=cassandra" \
     -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
     -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
     -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
     -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
     -p 8000:8000 \
     -p 8443:8443 \
     -p 8001:8001 \
     -p 8444:8444 \
     -p 8005:8005 \
     kong:2.0.0

データプレーンのkong

$ docker run -d --name kong-dp \
     --network=kong-net \
     -v "kong-vol:/usr/local/kong" \
     -e "KONG_ROLE=data_plane" \
     -e "KONG_CLUSTER_CONTROL_PLANE=kong-cp:8005" \
     -e "KONG_CLUSTER_CERT=/usr/local/kong/cert/cluster.crt" \
     -e "KONG_CLUSTER_CERT_KEY=/usr/local/kong/cert/cluster.key" \
     -e "KONG_LUA_SSL_TRUSTED_CERTIFICATE=/usr/local/kong/cert/cluster.crt" \
     -e "KONG_DATABASE=off" \
     -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
     -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
     -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
     -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
     -p 9000:8000 \
     -p 9443:8443 \
     -p 9001:8001 \
     -p 9444:8444 \
     kong:2.0.0

クラスターの状態を確認

コンテナ確認

# docker ps
CONTAINER ID  IMAGE        COMMAND                  NAMES
2ff26508e7a2  kong:2.0.0   "/docker-entrypoint.…"  kong-dp
5b597f17cb3a  kong:2.0.0   "/docker-entrypoint.…"  kong-cp
028369c521c1  cassandra:3  "docker-entrypoint.s…"  kong-database

コントロールプレーンからクラスタを確認。kong-dp がいる。

curl http://localhost:8001/clustering/status | jq
{
  "cf749b5e-0463-4142-8f26-86bd8343c915": {
    "config_hash": "f8adac67f709388993f9c8b52fe5f6ca",
    "last_seen": 1580380612,
    "ip": "172.19.0.4",
    "hostname": "2ff26508e7a2"
  }
}

Configuring a Service を試す

https://docs.konghq.com/2.0.x/getting-started/configuring-a-service/

1. Add your Service using the Admin API

サービス追加

$ curl -i -X POST \
  --url http://localhost:8001/services/ \
  --data 'name=example-service' \
  --data 'url=http://mockbin.org'

HTTP/1.1 201 Created
Date: Thu, 30 Jan 2020 10:45:27 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/2.0.0
Content-Length: 296
X-Kong-Admin-Latency: 127

{"host":"mockbin.org",
"created_at":1580381127,
"connect_timeout":60000,
"id":"779925da-191c-487a-9666-5982f00b3fec",
"protocol":"http",
"name":"example-service",
"read_timeout":60000,
"port":80,
"path":null,
"updated_at":1580381127,
"retries":5,
"write_timeout":60000,
"tags":null,
"client_certificate":null}

サービス確認

$ curl -s http://localhost:8001/services | jq .
{
  "next": null,
  "data": [
    {
      "host": "mockbin.org",
      "created_at": 1580381127,
      "connect_timeout": 60000,
      "id": "779925da-191c-487a-9666-5982f00b3fec",
      "protocol": "http",
      "name": "example-service",
      "read_timeout": 60000,
      "port": 80,
      "path": null,
      "updated_at": 1580381127,
      "retries": 5,
      "write_timeout": 60000,
      "tags": null,
      "client_certificate": null
    }
  ]
}

2. Add a Route for the Service

ルート追加

$ curl -i -X POST \
  --url http://localhost:8001/services/example-service/routes \
  --data 'hosts[]=example.com'

HTTP/1.1 201 Created
Date: Thu, 30 Jan 2020 10:46:43 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/2.0.0
Content-Length: 429
X-Kong-Admin-Latency: 67

{"id":"59d80700-d503-4b1a-bafe-d5bdaa92cb0f",
"path_handling":"v0",
"paths":null,
"destinations":null,
"headers":null,
"protocols":["http","https"],
"methods":null,
"snis":null,
"service":{"id":"779925da-191c-487a-9666-5982f00b3fec"},
"name":null,
"strip_path":true,
"preserve_host":false,
"regex_priority":0,
"updated_at":1580381203,
"sources":null,
"hosts":["example.com"],
"https_redirect_status_code":426,
"tags":null,
"created_at":1580381203}

ルート確認

$ curl -s http://localhost:8001/services | jq .
{
  "next": null,
  "data": [
    {
      "host": "mockbin.org",
      "created_at": 1580381127,
      "connect_timeout": 60000,
      "id": "779925da-191c-487a-9666-5982f00b3fec",
      "protocol": "http",
      "name": "example-service",
      "read_timeout": 60000,
      "port": 80,
      "path": null,
      "updated_at": 1580381127,
      "retries": 5,
      "write_timeout": 60000,
      "tags": null,
      "client_certificate": null
    }
  ]
}

3. Forward your requests through Kong

curl -i -X GET \
  --url http://localhost:9000/ \
  --header 'Host: example.com'

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 10695
Connection: keep-alive
Server: Cowboy
Etag: W/"29c7-XG+PICJmz/J+UYWt5gkKqqAUXjc"
Vary: Accept-Encoding
Date: Thu, 30 Jan 2020 10:47:58 GMT
Via: kong/2.0.0
X-Kong-Upstream-Status: 200
X-Kong-Upstream-Latency: 367
X-Kong-Proxy-Latency: 70
Kong-Cloud-Request-ID: 782983cc8a4bdd4fb240efcfcf3546f1

<!DOCTYPE html><html><head><meta charset="utf-8"><title>Mockbin by Kong</title>...

あっさりできて不安になる。プラグイン、コンシューマ、プラグインはどう使うのか?

ローカルコンテナからリモートdockerを操作

コンテナからコンテナを操作するその3

リモートDockerホストへのSSH接続

# DOCKER_HOST=ssh://root@192.168.0.13
# export DOCKER_HOST
# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

# docker run hello-world
# docker run hello-world

# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
34f16b15d511        hello-world         "/hello"            38 seconds ago       Exited (0) 36 seconds ago                           trusting_wilson
5fb7cd049e78        hello-world         "/hello"            About a minute ago   Exited (0) About a minute ago                       affectionate_babbage

環境変数を削除すると、ローカルを参照する

# unset DOCKER_HOST
# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

ローカルコンテナからリモートコンテナを操作する

  • ソケットと.sshをマウント
  • DOCKER_HOST を指定

.ssh のマウントは良くないんだろうか?まあ実験てことで。。。

# docker run -it -v /var/run/docker.sock:/var/run/docker.sock \
         -v ~/.ssh:/root/.ssh \
         -e DOCKER_HOST=ssh://root@192.168.0.13 \
         --name some-docker -d docker:stable-dind sh
# docker exec -it some-docker sh
/ # docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
34f16b15d511        hello-world         "/hello"            24 minutes ago      Exited (0) 24 minutes ago                       trusting_wilson
5fb7cd049e78        hello-world         "/hello"            24 minutes ago      Exited (0) 24 minutes ago                       affectionate_babbage

/ # docker run hello-world
Hello from Docker!
:

/ # docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
96ba0be8b6d6        hello-world         "/hello"            19 seconds ago      Exited (0) 18 seconds ago                       vigorous_greider
34f16b15d511        hello-world         "/hello"            26 minutes ago      Exited (0) 26 minutes ago                       trusting_wilson
5fb7cd049e78        hello-world         "/hello"            26 minutes ago      Exited (0) 26 minutes ago                       affectionate_babbage

ローカルコンテナから出て ps

# docker ps -a
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS               NAMES
022cef67f0f6        docker:stable-dind   "dockerd-entrypoint.…"   3 minutes ago       Up 3 minutes        2375-2376/tcp       some-docker

ノード13 で ps

# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
96ba0be8b6d6        hello-world         "/hello"            About a minute ago   Exited (0) About a minute ago                       vigorous_greider
34f16b15d511        hello-world         "/hello"            28 minutes ago       Exited (0) 28 minutes ago                           trusting_wilson
5fb7cd049e78        hello-world         "/hello"            28 minutes ago       Exited (0) 28 minutes ago                           affectionate_babbage