- 投稿日:2020-02-28T21:49:35+09:00
GoのechoでRESTAPIを作る時にCORSで怒られた時の対処法
フロントエンドとバックエンドでドメインが違う場合、フロントエンドからバックエンドにfetchするとCORSで怒られる。
その際は以下のようにCORSWithConfigミドルウェアを設定してやればいい。server.goe := echo.New() e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: []string{"http:///*リクエスト送る側のurl*/"}, AllowHeaders: []string{"authorization", "Content-Type", "Access-Control- Allow-Origin"}, AllowCredentials: true, AllowMethods: []string{http.MethodGet, http.MethodPost}, })) e.POST("/login", login) e.GET("/logout", logout) e.Logger.Fatal(e.Start(":/*ポート番号*/")) func login(c echo.Context) error { /*handle login*/ } func logout(c echo.Context) error { /*handle logout*/ }
- 投稿日:2020-02-28T18:25:53+09:00
Prometheus+Grafanaでk8s上のGoアプリケーションのメトリクスを回収して可視化
Prometheus+Grafanaで、k8s上で動かしているGoアプリケーションのMemoryやゴルーチン起動数などのメトリクスを回収し可視化する
最終的なグラフ
構成図
この記事では、Prometheus, Grafanaもk8s内で動作させます。
メトリクスの回収としては、GoのアプリケーションにPrometheus Exporterを入れてメトリクスを公開し、Promehteusはサービスディスカバリを使って、そのメトリクスを回収という挙動になります。構築
kubernetesクラスタを立ち上げる
今回はEKSを利用します。eksctlを使うと簡単にEKSクラスタを立ち上げれます。
eksctl create cluster --name sample-cluster \ --version 1.14 \ --nodegroup-name sample-cluster-worker \ --node-type t3.small \ --nodes 3 \ --nodes-min 1 \ --nodes-max 5 \ --managedGoアプリケーションにメトリクス回収用の設定を入れる
ここでの説明で使うコードはこちらのリポジトリにおいてます
github.com/prometheus/client_golang
を利用してメトリクスを公開しますgo get github.com/prometheus/client_golangあとはコードに下記のようにメトリクス公開用のポートをあげれば、アプリ側の設定はokです。
import ( "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" ) func main() { http.Handle("/metrics", promhttp.Handler()) http.ListenAndServe(":2112", nil) }ちゃんと運用する場合は下記のように別スレッドで動かしてあげると良いでしょう
func main() { // 設定とかで、メトリクス公開設定とか入れとく if conf.COLLECT_METRICS { http.Handle("/metrics", promhttp.Handler()) go http.ListenAndServe(":2112", nil) } // メインの処理 // ... }一度ローカルで動作確認しておきましょう, ちゃんと2112ポートでメトリクスを回収できるか確認します
$ go run main.go $ curl localhost:2112/metrics # HELP go_gc_duration_seconds A summary of the GC invocation durations. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 0 go_gc_duration_seconds{quantile="0.25"} 0 go_gc_duration_seconds{quantile="0.5"} 0 go_gc_duration_seconds{quantile="0.75"} 0 go_gc_duration_seconds{quantile="1"} 0 go_gc_duration_seconds_sum 0 go_gc_duration_seconds_count 0 ...取れていますね。あとはこれをkubernetesにdeployします。
マニフェストで2112ポートを開けておいてくださいdeployment.ymlapiVersion: apps/v1 kind: Deployment metadata: name: sample-go-app spec: selector: matchLabels: app: prometheus-export-sample replicas: 4 template: metadata: labels: app: prometheus-export-sample spec: containers: - name: prometheus-export-sample image: esaka/prometheus-export-sample ports: - containerPort: 2112Prometheus+Grafanaをデプロイ
こちらのマニフェストをベースにして、いくつか設定を変えたものが以下になります。
https://github.com/esakat/prometheus-exporter-sample/blob/master/prometheus-grafana.yml
変更点
- Dashboardのimport設定削除
- GrafanaやPrometheusのimageを新しいものに変更
- GrafanaのサービスをLoadBalnacerに変更
- prometheus.ymlにGoのメトリクス回収設定を追加(後述)
Goのメトリクス回収用設定を追加する
prometheus.ymlに以下のような, Goアプリケーション回収用の設定を追加します。
prometheus.yml- job_name: 'go-metrics' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: - __meta_kubernetes_namespace - __meta_kubernetes_pod_container_port_number regex: default;2112 action: keep - source_labels: - __meta_kubernetes_pod_name target_label: job - source_labels: - __meta_kubernetes_pod_name target_label: podGrafanaにログイン
以下のコマンドで作成されたELBのアドレスを確認してください
立ち上がるまでは少し時間かかります。$ kubectl get svc -n monitoring grafana NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE grafana LoadBalancer 10.100.191.149 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.ap-northeast-1.elb.amazonaws.com 3000:32017/TCP 7m46s立ち上がったら、3000ポート指定でGrafanaへ繋がります。
http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.ap-northeast-1.elb.amazonaws.com:3000ログインユーザーとパスワードは
admin:admin
ですPrometheusをデータソースとして設定
データソース設定追加でPrometheusを選択し
URLにhttp://prometheus:9090
を設定しSave & Test
でおkです。Dashboardを作成
Grafanaのサイトに行くと、色々ダッシュボードが転がっています。
特にこだわりなければ、この中からGoアプリケーション可視化用のダッシュボードを使うと楽でしょうhttps://grafana.com/grafana/dashboards/6671
こちらのダッシュボードがおすすめです。
Download Jsonでjsonファイルを落としておきます。そしたら
http://<host>:3000/dashboard/import
にアクセスしUpload .json file
ボタンを押して、先ほど落としたgo-processes_rev2.json
をアップロードします。
そしたら、prometheus-apl
でPrometheus
を選択してImport
をすれば、以下のようなダッシュボードが見れるようになります先ほどデプロイしたsample-go-appのメトリクスが取れています。
まとめ
GoアプリにPrometheus Exporterを仕込んで、Prometheusにメトリクスを回収させ
Grafanaで可視化させました
- 投稿日:2020-02-28T14:56:37+09:00
golangのmongo-go-driverを使うとstructのtime.TimeにUTCで日付が入るのでlocal-timeにする方法
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) opt := options.Client().ApplyURI("mongodb://localhost:27017") // findでstructにtime.Time型が設定されている状態で、取得するとUTCで設定されるのでLocalTimeZoneになるようにした。 rb := bson.NewRegistryBuilder() // v1.2.1より前 // rb.RegisterDecoder(reflect.TypeOf(time.Time{}), bsoncodec.NewTimeCodec(bsonoptions.TimeCodec().SetUseLocalTimeZone(true))) // v1.3.0以降 メソッドが変更になっている。。。 rb.RegisterTypeDecoder(reflect.TypeOf(time.Time{}), bsoncodec.NewTimeCodec(bsonoptions.TimeCodec().SetUseLocalTimeZone(true))) opt.SetRegistry(rb.Build()) client, _ := mongo.Connect(ctx, opt) col := client.Database("hoge").Collection("fuga") type fuga struct { T1 time.Time `bson:"t1"` T2 primitive.DateTime `bson:"t2"` } now := time.Now() col.DeleteMany(ctx, &map[string]interface{}{}) col.InsertOne(ctx, &map[string]interface{}{"t1": now, "t2": now}) result := &fuga{} sr := col.FindOne(ctx, &map[string]interface{}{}) sr.Decode(result) fmt.Println(result.T1) fmt.Println(result.T2.Time()) // registryセット前 // 2020-02-28 05:50:26.759 +0000 UTC // 2020-02-28 14:50:26.759 +0900 JST // registryセット後 // 2020-02-28 14:52:51.103 +0900 JST // 2020-02-28 14:52:51.103 +0900 JSTなお、mapにデコードすると、
primitive.DateTime
でデコードされるのでJSTになるっぽい。所感
ドキュメントに無いから、実装を追ってみた。
コード追うの大変だったので、ぜひドキュメントに追記いただきたい。。。
あと、設定してすぐに、v1.3.0でメソッド名が変わったので、コンボ食らった気分だった。。。
- 投稿日:2020-02-28T13:09:58+09:00
最も簡単にパワフルなGolangのvim開発環境をSpaceVimで構築する
はじめに
Vimでgolangを書く環境を整えたいと思っていたところ、SpaceVimを使ってそれがとても簡単にかつ満足度高く実現出来たので書いていきたいと思います。
前提としてSpaceVimが適用されていることが前提となりますので、インストールされていない方は、こちらで設定を行なってください。
Golangのインストールなどについても解説しません。layersの追加
このlayerを使います。
~/.SpaceVim.d/init.toml
に以下の記述を追加します。[[layers]] name = "lang#go"一度保存して、vimを開いてください。
vimのnormalモードで以下を実行します。:GoInstallBinaries
上記実行後、gotagsをインストールするため、以下もターミナルで実行しておきましょう。
$ go get -u github.com/jstemmer/gotags使い方
key-bindingsに詳しい使い方は書いてありますが、以下に少し書いておきます。
キーバインド 実行内容 space + l + b go build space + l + r go run space + l + h go info space + l + M add import space + l + t go test
- 投稿日:2020-02-28T08:52:12+09:00
関数とメソッドの違い【golang】
オブジェクト指向言語では
関数
- オブジェクト関係なく、呼ぶと何か処理をする
プログラムの部品
メソッド
- オブジェクト指向で登場する用語で、
オブジェクトの動作を定義したもの
らしい。golangはオブジェクト指向??
- golangを勉強し始めて1週間。
- 今まで、同義的に使っていたメソッドと関数という言葉・・・
- golangがオブジェクト指向なのかはよくわからない(オブジェクト指向言語としてGolangをやろうとするとハマること)けど、まとめてみたらオブジェクト指向言語っぽく使えることを理解した。
- とにかく、関数とメソッドは明確に違いがあるらしいとのことでまとめてみた
- 諸先輩方、認識が違ったらご指摘お願いいたします。
- ※比較しやすいよう同じ処理をする
関数
func
で始まる処理の塊の中で構造体(型)に紐づけられていないものmain.gotype Square struct { X, Y int } func Area(s Square) int { return s.X * s.Y } func main() { s := Square{5, 5} fmt.Println(Area(s)) // >>25 }メソッド
- 構造体(型)に紐づく処理の塊
Area(s Square)
の部分が↑の型に紐づく
を実装している部分- こうすることで
Area
は構造体:Square
の持ち物となり、s.Area()
として呼び出すことができる。これがメソッド。- この紐づけた構造体(型)のことを
レシーバ(receiver)
と呼ぶらしい。main.gotype Square struct { X, Y int } func Area(s Square) int { return s.X * s.Y } func main() { s := Square{5, 5} fmt.Println(s.Area()) // >>25 }ポインタレシーバ
- ↑の
レシーバ
を使用して実態を書き換えることをポインタレシーバ
と呼ぶらしい。耳なじみのある言い方で言うと参照渡し
。- 例えば、SquareのArea()の前にSquareを拡張する
Scale
というメソッドを用意してみる。実態が変わるため出力は2500
となる。(s *Square) Scale
の部分のアスタリスク(*)を削除すると値レシーバ(値渡し)
となり出力は25
となるのでコチラでこのソースをコピペして試してみてください。main.gotype Square struct { X, Y int } func (s Square) Area() int { return s.X * s.Y } func (s *Square) Scale(i int) { s.X = s.X * i s.Y = s.Y * i } func Area(s Square) int { return s.X * s.Y } func main() { s := Square{5, 5} s.Scale(10) fmt.Println(s.Area()) // >> 2500 }
- とりあえず、関数とメソッドの違いでした。
- 投稿日:2020-02-28T00:46:02+09:00
ランダムに抽選する関数の確率テスト(Golang)
自作した社員抽選Slack Botの抽選確率がランダムになっているか調査するテストを書いてみました
Go + AWS LambdaでSlackの社員抽選botを作った話
https://blog.acall.jp/2019/11/develop-slack-lottery-bot/テスト対象関数
main.gopackage main import ( "math/rand" "time" ) func lotteryOneUserFromUsers(userIDs []string) string { rand.Seed(time.Now().UnixNano()) userID := userIDs[rand.Intn(len(userIDs))] return userID }スライスを渡すと要素の1つがランダムで返ってきます
とてもシンプルな関数ですテストコード
main_test.gopackage main import ( "fmt" "math" "testing" ) func Test_lotteryOneUserFromUsers(t *testing.T) { // initialize userIDs := []string{"a", "b", "c", "d", "e"} counts := make(map[string]int) for _, id := range userIDs { counts[id] = 0 } // sampling const loopCnt int = 1000 var i int var userID string for i < loopCnt { userID = lotteryOneUserFromUsers(userIDs) counts[userID]++ i++ } // calculation const allowErrRate = 0.05 var probability float64 var errRate float64 expectProbability := float64(1) / float64(len(userIDs)) fmt.Printf("ID: count, percentage\n") for k, v := range counts { probability = float64(v) / float64(loopCnt) errRate = math.Abs(expectProbability - probability) fmt.Printf("%s: %d ", k, v) fmt.Printf("%v per \n", probability*100.0) if errRate > allowErrRate { t.Errorf("Error Rate is too big. Label: %s, Error Rate: %f", k, errRate) } } }初期化
userIDs := []string{"a", "b", "c", "d", "e"} counts := make(map[string]int) for _, id := range userIDs { counts[id] = 0 }userIDsに適当な引数定義しています
このuserIDsの抽選を何度も繰り返し、確率を求めますcountsは当選回数を格納するmapです
サンプリング
const loopCnt int = 1000 var i int var userID string for i < loopCnt { userID = lotteryOneUserFromUsers(userIDs) counts[userID]++ i++ }loopCntにループ回数を定義し、当選回数をcounts追加するループを回しています
確率の計算
const allowErrRate = 0.05 var probability float64 var errRate float64 expectProbability := float64(1) / float64(len(userIDs)) fmt.Printf("ID: count, percentage\n") for k, v := range counts { probability = float64(v) / float64(loopCnt) errRate = math.Abs(expectProbability - probability) fmt.Printf("%s: %d ", k, v) fmt.Printf("%v per \n", probability*100.0) if errRate > allowErrRate { t.Errorf("Error Rate is too big. Label: %s, Error Rate: %f", k, errRate) } }
変数 説明 probability 計算した確率 expectProbability 理想確率 errRate 計算した確率誤差 allowErrRate 許容確率誤差 確率の理想値と実際の確率の差を求めて許容確率誤差を超えていないか確認するだけです
実行
go test . -v
でテストを実行してみます
-v
はテストPASS時にも標準出力を表示するオプションです$ go test . -v === RUN Test_lotteryOneUserFromUsers ID: count, percentage a: 202 20.200000000000003 per b: 205 20.5 per c: 209 20.9 per d: 212 21.2 per e: 172 17.2 per --- PASS: Test_lotteryOneUserFromUsers (0.01s) PASS ok lottery 0.021sPASSしてますね
許容確率誤差をもっと小さくしてみると失敗するようになります許容誤差0.01の場合$ go test . -v === RUN Test_lotteryOneUserFromUsers ID: count c: 193 19.3 per d: 219 21.9 per e: 190 19 per a: 182 18.2 per b: 216 21.6 per --- FAIL: Test_lotteryOneUserFromUsers (0.01s) handler_test.go:41: Error Rate is big. Label: d, Error Rate: 0.019000 handler_test.go:41: Error Rate is big. Label: a, Error Rate: 0.018000 handler_test.go:41: Error Rate is big. Label: b, Error Rate: 0.016000 FAIL FAIL lottery 0.021s FAILまとめ
「このbot確率偏ってるんじゃない?」と聞かれた時はこの結果を見せて正しいロジックであることを証明したいと思います