20200521のGoに関する記事は5件です。

Goで書いたサーバーをCloud Runで動かす

Goで書いたサーバーをデプロイする先を探していて、Google App Engine(SE)ArukasHerokuなどを試してきたが、今回は新たにCloud Runを試したのでその手順をここに記しておく。

前提

  • Google Cloud Platformのアカウント(請求先の登録が必要)
  • Google Cloud Platformのプロジェクト作成
  • Google Cloud SDKのインストール
  • Dockerがインストールされていること+起動していること

この辺りの説明は省略。

Cloud Runの有効化(初回のみ)

enable cloud run

Cloud consoleでCloud Runのページを開き、Cloud Runを有効にしておく。フルマネージド環境で使う場合は「CLOUD RUNの使用を開始する」を選べばOK。

検証用サーバープログラムの準備

リクエストがくるとHello worldを返すだけのシンプルなHTTPサーバーを用意した。
Cloud Runで動かすコンテナは、環境変数PORTで指定されたポートでListenできるようにしておかなければいけないので、Listenするポート番号を引数で指定できるようにしている。(環境変数を直接参照しても良いが)

https://cloud.google.com/run/docs/reference/container-contract?hl=ja#port

main.go
package main

import (
    "fmt"
    "net/http"
    "os"
    "strconv"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello world\n")
}

func main() {
    port, _ := strconv.Atoi(os.Args[1])
    fmt.Printf("Starting server at Port %d", port)
    http.HandleFunc("/", handler)
    http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}


動作確認のため、ローカル3000版ポート指定で起動してみる。

go run main.go 3000

別のShellからcurlでリクエストを送ってみて、Hello worldが返ってきたらOK。サーバーを停止する。

curl http://localhost:3000/

Dockerイメージの作成

Dockerfile
FROM golang:latest as builder

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
WORKDIR /go/src/github.com/yokoe/cloudrunexp
COPY . .
RUN go build main.go

# runtime image
FROM alpine
COPY --from=builder /go/src/github.com/yokoe/cloudrunexp /app

CMD /app/main $PORT

alpineをベースに、最小限のサーバーのイメージを作る。サーバー起動時の引数に環境変数$PORTを渡すことで、$PORT番号のポートでListenするようにする。

docker build -t cloudrunexp .

ビルドに失敗する場合はDockerが起動しているか確認する。

docker run -e "PORT=3000" -p 3000:3000 -t cloudrunexp

動作確認用に$PORT=3000で、サーバーを起動してみる。ポートフォワーディングをして、http://localhost:3000/でコンテナ内のサーバーにアクセスし、レスポンスが返ってくることを確認する。

curl http://localhost:3000/

Container RegistryへのPush

作成したイメージをGoogle Container Registry(GCR)にPushする。

Container Registry APIの有効化(初回のみ)

Container Registry APIを有効にしたことがない場合は、コンソールから有効にしておく。

gcrapi.png

Docker認証ヘルパーの有効化(初回のみ)

gcloud auth login
gcloud auth configure-docker

gcloudで認証し、Docker認証ヘルパーを有効にすることで、docker pushコマンドでGCRにイメージをPushできるようになる。

https://cloud.google.com/container-registry/docs/advanced-authentication?hl=ja

タグをつける

docker tag cloudrunexp gcr.io/go-server-exp/cloudrunexp:firstbuild

アップデートしていく時に、どのイメージがどのビルドかわかるようにタグをつけておく。今回は最初なのでfirstbuildというタグをつけた。

GCRにイメージをPushする

docker push gcr.io/go-server-exp/cloudrunexp:firstbuild

docker pushコマンドを使って、firstbuildタグのついたイメージをGCRにPushする。

スクリーンショット 2020-05-21 22.06.49.png

Pushに成功すると、Cloud consoleのContainer Registry画面でPushされたイメージのリストを確認できる。

サービスの作成

Container Registryでイメージの確認

スクリーンショット 2020-05-21 22.19.29.png

Container Registryのイメージのメニューから「Cloud Runへデプロイ」を選ぶと、Cloud Runのサービス作成画面が開く。

サービスの作成

create service

自分で管理しているGKE上で動かしたいわけではないので、フルマネージドを選ぶ。また、パブリック(誰でもアクセスできる)なウェブサーバーとして動かしたいので「未認証の呼び出しを許可」を選択。

スクリーンショット 2020-05-21 22.22.36.png

コンテナイメージが、先ほどContainer Registryで選んだイメージになっていることを確認して「作成」する。

スクリーンショット 2020-05-21 22.23.47.png

作成完了すると、早速利用可能となり、自動生成されたURLが表示される。アクセスしてみて、Hello worldが表示されれば成功。カスタムドメインを割り当てたい場合は、URL横のinfoボタンから、「MANAGE CUSTOM DOMAINS」に進み、マッピングを追加すれば簡単に利用できた。

なお、今回はサービスの設定、デプロイはWeb UI経由で行ったがCI経由、Terraformなどを使っても操作できる。

感想

GKEでDeploymentやService、Ingressの設定をするのに比べると非常に楽だった。カスタムドメインの割当ても簡単だったし、ちょっとしたHTTPサーバーを立ち上げるのには結構良さそう。

HerokuのDocker Deployも同様に簡単で良かったが、GCSやCloudSQLなどGCP系のサービスと組み合わせる場合はCloud Runも良さそう。

参考

https://cloud.google.com/container-registry/docs/quickstart?hl=ja

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

巨大なデータを取得する関数でデータを戻り値にする代わり

sink って名前がわかりやすいなと思ったので忘れないようにメモ。

Goで巨大なデータを取得する処理の場合、配列に入れて返すのどうなんってなりがち。

func FetchTooBigData() ([]Datum, error) {
  var data []Datum
  ...
  data = append(data, Datum{...})
  ...
  return data
}

メモリに全部載せるの流石にキツイ。

受け取るための某を作って引数で渡してやるのが設計的に簡単で良さそう。

type DataSink interface {
  Add(Datum) error
}

func FetchTooBigData(sink DataSink) error {
  ...
  if err := sink.Add(Datum{...}); err != nil {
    return err
  }
  ...
}

もっとちゃんと作り込んで、他の言語でよく見るチェーンっぽい書き方をするなら

type EnumerableData interface {
  Next() bool
  Current() Datum
}

func FetchTooBigData() (EnumerableData, error) {
  ... // よしなに
}

data, err := FetchTooBigData()
if err != nil {
  panic(err)
}

for data.Next() {
  datum := data.Current()
  ...後続処理
}

みたいなコルーチンじみたアレを作るんだろうけど、流石に面倒くさい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Go]ElasticSearchの実行クエリをログ出力する

モチベーション

GoからElasticSearchを操作するクライアントとしてgithub.com/olivere/elasticがある。
実行時に展開される生queryをみたかった。以下のようにセットするとログ出力で確認できる。

sample.go
es, err := elastic.NewClient(
  // other option...
  elastic.SetTraceLog(log.New(os.Stdout, "[elastic-search]", 0)), // [elastic-search]はlog prefix
)

参考

https://github.com/olivere/elastic/wiki/Logging

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

golang 戻り値が関数の時の初期化的な流れ

関数が戻り値の時に、どういう流れで初期化されているのか確認してみた

関数を返す前までの準備はされた上で、関数が戻り値として帰る。その戻った関数が実行されるのは、それが実行される時(わかりにくい表現だなぁ)

closure.go
package main                                                                                       

import "fmt"

func intSeq() func() int {
    i := 0
    fmt.Println("init")
    return func() int {
        i += 1
        return i
    }   
}

func main() {
    fmt.Println("before init")
    nextInt := intSeq()
    fmt.Println("after init")

    fmt.Println(nextInt())
    fmt.Println(nextInt())
    fmt.Println(nextInt())
    newInts := intSeq()
    fmt.Println(newInts())
}      

結果

before init
init
after init
1
2
3
init
1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】http.ClientにRoot CAを読み込ませる

RootCAs が nil だと普通にPCの証明書を取りに行っちゃうのでRoot CAをハードコードする

これでBurp Suiteとかを使った中間者攻撃を対策できる・・・ハズ

main.go
    ca := `-----BEGIN CERTIFICATE-----
読み込ませる Root CA の中身
-----END CERTIFICATE-----`
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM([]byte(ca))

    c := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{
                RootCAs:      caCertPool,
            },
        },
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む