ローカル環境(kind)でingressを試す

はじめに

kind (kubernetes in docker)でIngressを作成したい場合、ingress controllerを使う必要があります。

しかし、ドキュメント通りに実行してもM1 macなどのApple シリコンでは動きません。

理由は hashicorp/http-echo が arm64に対応していないからです。

$ docker container run --rm hashicorp/http-echo:latest -text 'aaa'

WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
runtime: failed to create new OS thread (have 2 already; errno=22)
fatal error: newosproc

runtime stack:
runtime.throw(0x6955a0, 0x9)
    /usr/local/go/src/runtime/panic.go:596 +0x95
runtime.newosproc(0xc420022000, 0xc420032000)
    /usr/local/go/src/runtime/os_linux.go:163 +0x18c
runtime.newm(0x6a4348, 0x0)
    /usr/local/go/src/runtime/proc.go:1628 +0x137
runtime.main.func1()
    /usr/local/go/src/runtime/proc.go:126 +0x36
runtime.systemstack(0x7cc200)
    /usr/local/go/src/runtime/asm_amd64.s:327 +0x79
runtime.mstart()
    /usr/local/go/src/runtime/proc.go:1132

goroutine 1 [running]:
runtime.systemstack_switch()
    /usr/local/go/src/runtime/asm_amd64.s:281 fp=0xc42001e788 sp=0xc42001e780
runtime.main()
    /usr/local/go/src/runtime/proc.go:127 +0x6c fp=0xc42001e7e0 sp=0xc42001e788
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:2197 +0x1 fp=0xc42001e7e8 sp=0xc42001e7e0

今回はM1 Macでも動くように、修正します

クラスターを作成する

kind-config.yaml

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: ingress-nginx
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
$ kind create cluster --config kind-config.yaml
$ kind get clusters
ingress-nginx

$ kubectl config current-context
kind-ingress-nginx

ingress-nginxをapplyする

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

$ kubectl get pod -n ingress-nginx        
NAME                                       READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-8m6zg       0/1     Completed   0          35m
ingress-nginx-admission-patch-rc9bm        0/1     Completed   1          35m
ingress-nginx-controller-6bccc5966-249gx   1/1     Running     0          35m

$ kubectl api-resources | grep ingress
ingressclasses                                 networking.k8s.io/v1                   false        IngressClass
ingresses                         ing          networking.k8s.io/v1                   true         Ingress

$ kubectl get ingressclasses -n ingress-nginx
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       37m

$ kubectl get ingressclasses -n ingress-nginx nginx -o yaml
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"networking.k8s.io/v1","kind":"IngressClass","metadata":{"annotations":{},"labels":{"app.kubernetes.io/component":"controller","app.kubernetes.io/instance":"ingress-nginx","app.kubernetes.io/name":"ingress-nginx","app.kubernetes.io/part-of":"ingress-nginx","app.kubernetes.io/version":"1.5.1"},"name":"nginx"},"spec":{"controller":"k8s.io/ingress-nginx"}}
  creationTimestamp: "2022-11-11T14:16:22Z"
  generation: 1
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.5.1
  name: nginx
  resourceVersion: "1550"
  uid: 9ed127ea-e3e3-4051-910e-0486d1e9c53a
spec:
  controller: k8s.io/ingress-nginx

http-echoのイメージを作成する

ドキュメントのUsing Ingress ではPodやServiceをapplyすることになるのですが、先ほどお伝えしたようにM1 macではエラーになるので、イメージをhttp-echoのコードから作成します。

Dockerfile

FROM golang:1.19.3-bullseye as build
RUN git clone https://github.com/hashicorp/http-echo.git
RUN cd http-echo && CGO_ENABLED=0 go build

FROM gcr.io/distroless/static-debian11:debug
COPY --from=build /go/http-echo/http-echo /
ENTRYPOINT [ "/http-echo" ]
$ docker build . -t http-echo:0.0.1

$ docker image ls http-echo        
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
http-echo    0.0.1     500c71506129   3 days ago   9.86MB

クラスターにイメージを入れる

自分でbuildしたイメージをPodに指定して使用するには、 kind load docker-image コマンドを使ってクラスターにイメージを渡します

$ kind load docker-image http-echo:0.0.1 --name ingress-nginx
$ docker exec -it ingress-nginx-control-plane crictl images | grep http-echo
docker.io/library/http-echo                          0.0.1                500c71506129b       11.4MB

Loading an Image Into Your Cluster

Ingressを作成する

Using Ingressからマニフェストをコピーしてimageを変更します

app.yaml

kind: Pod
apiVersion: v1
metadata:
  name: foo-app
  labels:
    app: foo
spec:
  containers:
  - name: foo-app
 # image: hashicorp/http-echo:0.2.3 ではなく http-echo:0.0.1に変更
    image: http-echo:0.0.1
    args:
    - "-text=foo"
---
kind: Service
apiVersion: v1
metadata:
  name: foo-service
spec:
  selector:
    app: foo
  ports:
  # Default port used by the image
  - port: 5678
---
kind: Pod
apiVersion: v1
metadata:
  name: bar-app
  labels:
    app: bar
spec:
  containers:
  - name: bar-app
 # image: hashicorp/http-echo:0.2.3 ではなく http-echo:0.0.1に変更
    image: http-echo:0.0.1
    args:
    - "-text=bar"
---
kind: Service
apiVersion: v1
metadata:
  name: bar-service
spec:
  selector:
    app: bar
  ports:
  # Default port used by the image
  - port: 5678
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
spec:
  rules:
  - http:
      paths:
      - pathType: Prefix
        path: "/foo"
        backend:
          service:
            name: foo-service
            port:
              number: 5678
      - pathType: Prefix
        path: "/bar"
        backend:
          service:
            name: bar-service
            port:
              number: 5678
---
$ kubectl apply  -f app.yaml

$ kubectl get pod                 
NAME      READY   STATUS    RESTARTS   AGE
bar-app   1/1     Running   0          26s
foo-app   1/1     Running   0          26s

動作確認

$ curl localhost/foo
foo

$ curl localhost/bar
bar

M1 Macでも無事動くことが確認できました。

クラスター削除

$ kind delete cluster --name ingress-nginx

忙しい人向け: docker composeでnginxを立てる

ディレクトリ構造

$ tree               
.
├── README.md
├── conf.d
│   └── default.conf
└── docker-compose.yaml

docker-compose.yaml

version: '3.9'
services:
  app:
    image: nginx:1.23-alpine
    ports:
      - "8080:80"
    volumes:
      - "./conf.d:/etc/nginx/conf.d"

conf.d/default.conf

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

実行

$ docker compose up

ブラウザで localhost:8080 にアクセス

終了

$ docker compose down

go言語のライブラリ、samber/loのGroupByが便利!

はじめに

go言語で実装をしていると、特定の条件に従ってスライスを複数のスライスに分割したい、というケースに遭遇します。

イメージとしてはこんな感じです。

sliceA, sliceB := SplitSlice(slice)

素直に実装するならば、関数内部で条件分岐して対応しているケースが多いのではないでしょうか?

func SplitSlice(s []int) ([]int, []int) {
    sliceA := make([]int,0)
    sliceB := make([]int,0)

    for _, x := range slice {
        if condA {
            sliceA = append(sliceA, x)
        }
        if condB {
            sliceB = append(sliceB, x)
        }
    }
    return sliceA, sliceB
}

samber/lo

こういったケースは samber/lo のGroupByを使用すれば、簡単に実装できます。

以下はREADMEに書いているサンプルです。

func main() {
    list := []int{0, 1, 2, 3, 4, 5}

    result := lo.GroupBy(list, func(i int) int {
        return i % 3
    })

    for _, item := range result {
        fmt.Printf("%v\n", item)
        // [0 3]
        // [1 4]
        // [2 5]
    }
}

https://github.com/samber/lo#groupby

正直これだけだと、すぐに理解するのは難しいと思います。もう少し実際にありそうなサンプルコードを作ってみました。

userにはそれぞれロールがあり、データはどこかのサーバからhttpを通して取得することを想定しています。

今回はそのロールごとにスライスを分割する例を表しています。

type Role string

const (
    Admin     = Role("admin")
    Operator  = Role("operator")
    Developer = Role("developer")
)

type user struct {
    id   string
    role Role
}

func GetUsersFromHogeAPI() []user {
    return []user{
        {
            id:   "1",
            role: Admin,
        },
        {
            id:   "2",
            role: Operator,
        },
        {
            id:   "3",
            role: Developer,
        },
        {
            id:   "4",
            role: Developer,
        },
    }
}

func main() {
    users := GetUsersFromHogeAPI()

    // resultの型はmap[Role][]user
    result := lo.GroupBy(users, func(u user) Role {
        // mapのkeyは以下のreturnで決まる!!
        return u.role
    })

    fmt.Printf("%+v\n", result[Admin])  // [{id:1 role:admin}]
    fmt.Printf("%+v\n", result[Operator]) // [{id:2 role:operator}]
    fmt.Printf("%+v\n", result[Developer]) // [{id:3 role:developer} {id:4 role:developer}]
}

https://go.dev/play/p/sduv6XcX-8U

result変数は map[Role][]user 型となっており、keyにRoleを渡してあげるとuserのスライスが取得できます。

そのほかもこのライブラリにはたくさんの関数が用意されているので、mapやsliceをforで操作するときは使えそうな関数がないか探してみると実装が楽になるかもしれません。

最後に

このライブラリに対する感想は、実装で積極的に使ってもいいと思っています。

理由としては、

  • Filter, Reduce, Mapと見るだけで、データに対してどんな操作をしているのかすぐにわかる
  • 一つ一つの実装は数行なので、たとえメンテナンスされなくなったとしても自前で実装できる

です

実際 GruopByのソースコードを見てみると10行ぐらいであることがわかると思います。

func GroupBy[T any, U comparable](collection []T, iteratee func(item T) U) map[U][]T {
    result := map[U][]T{}

    for _, item := range collection {
        key := iteratee(item)

        result[key] = append(result[key], item)
    }

    return result
}

個人的におすすめなので、実際に使ってみてはどうでしょうか?

Professional Cloud Architect 合格しました

はじめに

2021年9月に転職してから半年(2022年3月)が経過し、Professional Cloud Architectを受験しに行きました

Associateに合格したときは以下にあります ktbbrk.hatenablog.com

対策

主に試験対策として使った教材は以下の2つです。

問題集はテスト1つあたり2 ~ 3周して約80%以上にしてから望みました。

Google Cloud Certified Professional Cloud Architect - whizlabs

2022 Latest Google Cloud Architect Practice Question - udemy

試験前日

sentinelというアプリをインストールしました

試験当日

当日はレンタルスペースを試験開始時間30分前から3時間借りてオンラインで受けました

持っていったものは

です

開始前

Webassessorというサイトでアカウントを作成する必要があるのですが、名前を漢字で入力したので身分証明書として運転免許証を使いました

予約した時間10分前ぐらいからWebassessorのホーム画面にある登録済み試験のボタンからいけるページのスケジュール済みの試験開始ボタンがあるので、それをクリックして始めます

前回のAssociateの試験のときと違い、試験監督者とは日本語でチャットしました。

部屋をカメラで映す必要があるので、Mac Bookを手に持って部屋の周り、天井、床をそれぞれ5秒ずつ映すと"もう大丈夫"と言われました

そしてMac Bookと充電コードを映すように言われスマホで写真をとって見せ、最後にPCの全面を見せてくれと言われるのでそれもスマホで写真を撮って見せると確認が終わったらしく、試験がスタートしました

所感

実際の試験では問題集と同じ問題が出題されたのでUdemy等で対策は必要だと思いました。

しかし、実際に自分で手を動かしたほうが、理解が早くなるので実務で触れる機会があるなら積極的に触ったほうがいいと思っています

最後に

Associateに合格してからは実務でGCPを触ることはほとんどなかったですが、問題集をやれば意外に合格することがわかりました

golang genericsでswitch-caseをする

はじめに

Go言語では1.18でgenericsの機能が入ってくるので勉強していたのですが引数に型パラメータがあった場合どのようにswitchするのか気になったので備忘録を残します

引数の型が interface{}のとき

proposalに書いてるswitch文を見てみるとcaseの部分で型パラメータが使われている点以外、引数は interface{}になっており、普段go言語を書いている通りの記述になると思います

Generic types as type switch cases - Type Parameters Proposal

type Mystruct struct {
    fieldA string
}

func Switch[T any](v interface{}) (T, bool) {
    switch v := v.(type) {
    case T:
        return v, true
    default:
        var zero T
        return zero, false
    }
}

func main() {
    x, ok := Switch[string]("string")
    if ok {
        fmt.Println(x)  // string
    }

    y, ok := Switch[Mystruct](Mystruct{fieldA: "a"})
    if ok {
        fmt.Println(y) // {a}
    }
}

https://gotipplay.golang.org/p/nQJNsMiwkzh

引数の型が型パラメータの場合

では、関数の引数の型が型パラメータだった場合にどのようにswitchを使うのかをprosalから探してみます

サンプルコードがありました。(※少しだけコードを変更しています)
Identifying the matched predeclared type - Type Parameters Proposal

type Float interface {
    ~float32 | ~float64
}

func NewtonSqrt[T Float](v T) (int, T) {
    var iterations int
    switch (interface{})(v).(type) {
    case float32:
        iterations = 4
    case float64:
        iterations = 5
    default:  // panic: unexpected type main.MyFloat
        panic(fmt.Sprintf("unexpected type %T", v))
    }
    return iterations, v
}

type MyFloat float32

func main() {
    iterations, x := NewtonSqrt(MyFloat(64))

    fmt.Println(iterations)
    fmt.Println(x)
}

https://gotipplay.golang.org/p/te5gOiFyx2I

proposalを読んでみると以下のように書いてありました

The design doesn't provide any way to test the underlying type matched by a ~T constraint element.

つまり、現時点 (2022/1/29)ではunderlying typeのswitch文は書けそうにありません。
しかし、あくまでunderlying typeのときの話なので、今度はMyFloatではなくfloat型を関数に渡してみた結果無事に結果が表示されました。

サンプルコード

最後に

type switchに関するproposalが出ているので、将来的にやりたいことはできるのではと思っています

これからもproposalやリリース情報は追った方がよさそうです

github.com

Grafana 8のアラートはprovisioningできないのか?

はじめに

Grafanaでalertの設定 (Notification policies, Contact points)をしたいときダッシュボードのjsonファイルのように通知先の設定もyamlなどで定義したいと考えると思います

同じく悩んでいる人がいることが以下のリンクから確認できます

Provisioning contact points - Configuration - Grafana Labs Community Forums

※ この記事は 2022年1月25日に書いており、以下の画像のGrafanaはv8.3.3を使用しています。 f:id:kntks:20220125232740p:plain

※ちなみにこの時点の最新バージョンはv8.3.4です
f:id:kntks:20220126003018p:plain

結論

2022年1月現在、まだ宣言的に設定をすることができないようです。

currently, provisioning is not supported for Grafana 8 alerts

[ngalert] Provisioning of notifiers/contact points not working on Grafana 8 · Issue #36153 · grafana/grafana · GitHub

現在 issue #36153のコメントでは、

so perhaps 8.4/8.5 is a reasonable estimate at this time

とあり、マイルストーンにもタスクが追加されたので、8.4/.8.5らへんでリリースされるかもしれません

8.5.0 Milestone · GitHub

まとめ

将来的にリリースされることはほぼ確定だと思うので、リリースが楽しみですね

最後に

Grafana 8.2.0-beta2のリリースでは ngalert feature toggleが非推奨になりました

そのためgithubのissueやblogなどでngalertが出てきても、それはおそらく古い記事なのでGrafanaのバージョンには注意しましょう

Release notes for Grafana 8.2.0-beta2 | Grafana Labs

TweetDeckにリストからtweetを検索する方法

ケース

複数アカウントから特定のtweetのみ検索したい、でもリツイートは表示したくない

手順

まずはじめに検索の対象にしたいアカウントのリストを作成します。作成の仕方は以下のリンクを参考にしてください

help.twitter.com

作成が終わり、リストを表示するとlistの番号(白く塗りつぶしてある部分)がurlに書いてあると思います。この番号をコピーしておきます

f:id:kntks:20220102123717p:plain

次にTweetDeckに移行します

調べたい単語を入力してカラムを作成します

今回は dockerにします

f:id:kntks:20220102122327p:plain

tweet authorsmembers of List...を選択し、先ほどコピーしたlistの番号を直接指定します

f:id:kntks:20220102123702p:plain

そうするとリストに指定したアカウントから特定のtweeetを検索することができます

参考

【Twitter】リスト内のツイートを検索するには?手順を解説! | APPTOPI

【Twitter】リツイート・リプライを除外して純粋にフォロワーのコメントに絞る方法 - あなたのスイッチを押すブログ

The Most Comprehensive TweetDeck Research Guide In Existence (Probably) - bellingcat

How do I search within a Twitter list? - Web Applications Stack Exchange