Go言語初心者がハマるGo言語の細かい文法などの気付き
随時更新
goではintの計算結果は、intになる。
なので、
<int>10 / <int>100
の計算結果は、0になる。
切り捨てか切り上げか四捨五入かは、調べてない。
なので、予めfloat32
とかにcastしておく必要がある。
ポインタは変数に入れないと使用できないケースがある
ポインタ型を渡したいときに
hoge := "hoge" // string型の変数hogeに代入できます fmt.Println(hoge) p := &hoge // 変数hogeのポインタ(アドレス)を取得できます fmt.Println(p) // 変数hogeのポインタ(アドレス)を表示できます fmt.Println(*p) // 変数hogeのポインタ(アドレス)の中に入っている値を表示できます p2 := &"hoge" // これで値"hoge"が入っている値をセットしたいところですが、エラーになります。 fmt.Println(p2)
ポインタは、変数のアドレスを取得できるやつなので、一度変数に入れないとだめです。
githubのhttpsのcloneで`repository not found`のエラー
$ git clone https://github.com/my0shym/myproject
でrepository not found
のエラーが発生
今まではパスワードが聞かれていたはずなのだが、それもなくエラーになっている。
以前パスワードを聞かれて入力したらエラーになったことはあったが、それはパスワードではなくtokenを入力する必要があるように変わったことが原因だった。
しかし、今回はそもそもパスワード(トークン)すら聞かれない。
調べてみると、どうやらmacではbrewでinstallしたgitではkeychainにパスワードが初回に保存されていて、毎回入力しなくて良いようにそこから認証を通そうとしているとのことだった。
これも設定で変更できるのだが。
osxkeychain
なるオプションを指定すれば平文でパスワードを保存することもなく、便利に使えそうだった。
$git config --global credential.helper osxkeychain
を実行しても何も変化がなく(ここでパスワード(token)を設定するのかと思った)、依然として動かない。
色々試行錯誤してみたところ、解決。
結論としては、自分のmacに一番最初に設定したのが別のgithubアカウントのtokenだったことが原因でした。
複数アカウントをsshとかで使っちゃうと運用が面倒くさくなると思ったので、そっちのアカウントはsshじゃなくhttpsで、本アカウントをsshとして、サブアカウントで毎回httpsで操作しようとするときはtoken入力すればいいやくらいに思っていて特に棲み分けは考えなくてもいいやと思っていたところ、実は裏側でtokenが勝手に設定されていてそれを使いまわそうとされていたのでした〜
このやり方でパスワードを再設定したら、いけました!
しかし、となるとサブ垢で使っているgithubアカウントでまたhttps操作をしようとすると同じ問題が発生するので、これをどう対処するか。。という問題は残っていますね。
それはまた直面したときに。
たぶんそもそもhttpsを使わずに、configで分けておくのが良いのだと思っているのだがー。メモ。
それを言い出すと、そもそもgithubアカウントの複数運用自体が、無駄な工数という説もある。メモ。
goのginをdocker-composeでローカル環境で動かす
以下のような構成で作成しました。
main.go作成後、以下を実行します。
ファイルの内容はこの通り
Dockerfile
main.go
docker-compose.yml
ハマったところとしては、ginのチュートリアルで
router.Run("localhost:8080")
としてサーバーを動かしていたのでそのままやっていたら、コンテナ内で
をやっている分にはレスポンスが返ってきていたのですが、ホストからアクセスしようとすると、そもそもの疎通ができていない状態になってしまっていました。
他の記事等を見ているとlocalhostなしでRun(:8080)としているコードが多かったのでやってみたらこれでいけました。
うーんしかし根本の原因はちょっとよくわからなかったです。
コンテナ力がちょっと足りていないなあ。精進します。
あと微妙にハマったのは、ローカルmacでgo mod tidyでgo.sumを生成していたのですが、ローカルで使っていたgoのバージョンとDockerfileで使っていたgoのイメージのバージョンが違っていたがために、go.sumが整合性取れなくなっていて動かなかった問題もありました。
本当はalpine使ったほうが良かったのかもしれないですが、動いているので大目に見てもらいます。
ともあれ、これで動きました!
ここから開発にブーストかけていきたいと思います!
Go言語でCloud SQLとCloud Runを連携する奮闘記
接続しているっぽいところまでは割と簡単に進めましたが、本当に接続している?っていう確認に少し手間がかかりそうな気がしました。
動作確認の手順として
1. goでDBに接続してレコードを取ってきて表示するようなコードを書く
2. 1をイメージとしてArtifact Registryにpush
3. Cloud SQLのインスタンスを作成する、データを作っておく
4. Artifact RegistryからCloud Runサービスをデプロイ(Cloud SQLに接続する設定)
5. Cloud RunがDBからレコードを取得できていることをブラウザから確認
という手順で進めていきたいです。
cloud sql自体の作成やデータアクセスはこちらから確認できますが、他のリソースからアクセスをさせたいです。
ローカルからアクセスする場合には、プロキシなるものを用意しないといけないっぽい。
でもGCP内でやり取りするなら、たぶんそんなのはいらない気がする。
これやったら動きそうな気がしてるけど、goのコードが長い。。
もう少しシンプルに接続だけを確認したい。
これは甘えか?
普通にこれでいいのでは?
接続方法は書いてありそうだけど、goのアプリケーションで、どうやって使うのかがわからないな。
これはgoの知識不足か。
普通にレコードselectができればいいんですが。
アプリケーションでの使用も書いてあるが、ちと情報量多いな。
シンプルに、コネクションを作った後の動きは別でgoのsqlパッケージの使い方として調べます。
これを実装してみるか。
ローカルのmysqlと接続してみることにする。
いや、これの例の通りmysqlもdockerで立ち上げることにする。
とすると、エラーになりました。
本質的な解決策じゃないようだが、とりあえず問題は解決するとのこと。
のようにplatform: linux/x86_64を追加してdocker compose upすると、うまく立ち上がった。
で接続後、以下実行
最終的には以下のdocker-compose.yml
これに対して、接続するようなgoのコードを作る。
main.go
goの外部パッケージ追加方法は、まずmain.goと同じディレクトリ内で
で始めてから、
main.goにコードを書いた状態で、
をする。
すると、その中でimportしているがmodに追加されていないものを自動でimportしてくれる。
これで疎通確認できました!
これでとりあえず、mysqlからデータを取得するgoのコードが確認できました。
中身も見てみて、なるほどねって感じです。Goライクな書き方にだんだんと慣れてきている気がします。
これをそのままCloud Runにデプロイしようと思います。
Dockerfileは公式より、
これをベースにして、
まずは疎通確認のため、goのコードもこのサンプルのまま使います。
main.go
タグを付けてイメージをbuildする
Artifact Registryにpushする
Artifact Registryにimageがpushされているのを確認したら、コンソールからCloud Runサービスをデプロイ
おっと、ここでエラー。
これは以前解決したエラーでした。
ここで解決していますが、buildコマンドを以下に変更です。
再度pushして、Artifact RegistryからCloud Runにデプロイします。
公開されているURLからアクセスすると、
Hello, Docker! <3
の文字が表示されていました。
これでDockerfileが正しく動くことが確認できました。
これを次は、先程のDB接続用のmain.goに変更してpushします。
本当はDB接続情報は環境変数にセットして使用したいところですが、一旦決め打ちで作って動作を確認してから環境変数化していきたいと思います。
と思ったけど、このままだと、サーバーとしての機能は持たせていないからhttpリクエスト送ってもだめだ。
まずサーバーとしての機能は維持しつつ、handefuncでmysqlからデータを取得するように組み合わせる必要があるな。
Dockerfileのサンプルコードを使ってもいいんだけど、echoのモジュールを使ってリクエストの処理をしていたので、これは使わないで、シンプルにnet/httpの標準モジュールを使っていく。
以下より、まずはシンプルなwebサーバーとして動かす。
すごい回り道してきたきがするなぁ。
これをpushしてArtifactからCloud Runのサービスデプロイ。
→ URLアクセスでHello, Worldが表示された!
これのHandleFuncにmysqlからデータを取ってくる関数を追加しよう。
これでデプロイすると、
/ ではHello, worldが表示されるが、
/sql-man ではService Unavailable と表示される。
これはcloud sqlをまだ設定していないので正しい。
ここからcloud sqlの設定を入れていく。
と、公式のつなぎ込み方を再度確認すると、自分のコードではtcpで疎通している部分がunixソケットで疎通している。
unixソケットについて整理します。
・UNIX ドメインソケットもソケットもいずれもプロセス間のデータのやり取りを行うための手法の一つ
・UNIX ドメインソケットではプロセス間通信にファイルシステムを利用する(拡張子 .sock という場合が多い)その為、 同じホストでのプロセス間通信 として利用される
・ソケット通信は 異なるホスト で TCP や UDP を使ってプロセス間のやり取りが可能
Unix socket connectionってので通信しているんだが、これはソケット通信なのか、unixドメインソケット通信なのかどっちなんだ?
そこまでは把握する必要はないか?
形式的には
/cloudsql/project:region:instance
みたいな感じだから、異なるホストっていう扱いなのかな?
このあたりは宿題として放置。まず動くことを目標とします。
元々Cloud SQLのインスタンスは作成済みだったので、それを使用します。
以下コードで疎通の確認ができた!
これを一応環境変数を読み取るように変更する。
ここらへんは問題なく進めるはず。
→コメントアウト部分を復活させたものをArtifactにpushしてからCloud Runにデプロイ、その際に環境変数で
DB_USER
DB_PASS
DB_NAME
INSTANCE_UNIX_SOCKET
を設定したら、いけました!
くぅ〜これにて完結です。
本当は上の4つの値はシークレットマネージャーで管理したほうが良さそうなんだけども、それはまたのお話ということで。
どっかできれいにしてまとめたいです。
GoをCloud Runで動かしてみる。コードとDockerfileそれぞれでデプロイ
Goをデプロイしてみます。
前提条件として、GCPはちょっと知っていないとだめそうですね。
GCPは大昔触ったことがありましたが、プロジェクトとかの分け方がよくわかってないため、そのあたりからおさらいでやっていきます。
これを参考にしてやってみます。
まず、クイックスタートをやってみたいと思います。
その前に、公式のドキュメントもチェックしておきます。
チュートリアルは、なんかトピックが多すぎて、だめだ。
クイックスタートはなんか軽そうだし、上のQiitaの記事でも参考にしていたので、これを確認します。
ちょうどGoのやつがありました。これでいいじゃん。
「Cloud Shell」でできるみたいなので、これで進める。
1. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。
プロジェクトを作成します。
作成できました。
2. Cloud プロジェクトに対して課金が有効になっていることを確認します。詳しくは、プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。
大丈夫そうでした。というより、新規アカウントで作成したので、そのときに登録しました。
300$クレジットげっと!
3. Google Cloud CLI をインストールして初期化します。
3-1. gcloudをmacにinstallしてみます。
Quickstart: Install the Google Cloud CLI | Google Cloud CLI Documentation
と、これをやろうとしたのですが、brewでありそうなので、そっちでやってみます。
brewのバージョンを確認
installします。
完了後、以下を.zshrcに追記
以下実行
これでパスが通りましたわ。
初期化も必要みたいなので、やっておきます。
ブラウザで認証画面になるので、使いたいアカウントを設定します。
使用するプロジェクトもここで選ぶように言われるので選びます。
なんかzennの記事だとできなかったので、普通にbrewの指示通りにやったらできました。やはり公式が正義か。。
Cloud Run サービスのデフォルト プロジェクトを設定するには
(gcloud initで選択済みなのでこれは必要なさそうだったが、まあいい)
4. サンプル アプリケーションを作成する
helloworldディレクトリを作成して、
go.modファイルを以下で作成
以下でも良いようだが、goのバージョンが上がっていそうなので、素直に上記ファイルを作成した。
main.goを以下で作成
5. ソースから Cloud Run にデプロイする
main.goのあるディレクトリで以下コマンド
諸々聞かれるオプションを適当に設定していく。
と、エラーになった。
アカウント作ってから何もいじってないからadminユーザーとして実行しているような気がするんだがー
もう一回実行してみたら、行けました。
たぶん途中で待ち時間の間にオプションに変な文字が入ってしまっていて正しく入力できていなかったのかもしれません。
との表示があったのでアクセスすると、Hello, Worldの文字が!
成功!?
デプロイ簡単すぎわろたぁ〜
クイックスタートはここで終わりです。
すぐに終わって物足りない感あったので、もう少し見てみます。
<サービスを開発する>
軽く読みました。
<全般的な開発のヒント>
以下自分が気になった部分の引用と、まとめです。
・バックグラウンド アクティビティ
・Cloud Run はリクエストを 10 秒以上維持しないため、コンテナの起動に 10 秒以上かかる場合は、最小インスタンス数を 1 以上に設定する必要があります。
・Cloud Run では、リクエスト間でサービスの状態が維持されるとは限りません。ただし、Cloud Run はコンテナ インスタンスを再利用してトラフィックの処理を継続するため、グローバル スコープで変数を宣言することで、その値を以降の呼び出しで再利用できます。個々のリクエストで値が再利用されるかどうかを事前に確認することはできません。
・グローバル変数の扱いに注意する
・alpineかscratch等、小さいサイズのベースイメージを使用する。
<コンテナをビルドする>
Dockerfileからデプロイ
大枠はこれで良さそうですが、Dockerfileとか含め、実際に絶対に動くやつでまずは確認したいので、そういった情報を探してみます。
これを参考にしてやってみる。
新しいプロジェクトを作ってトライします。
`main.go`
以下実行
ブラウザからhttp://localhost:3000 にアクセス。
「Hello, world」が表示されました。良さそうです。
`Dockerfile`
→成功。
→http://localhost:3000 にアクセスして成功。hello, worldが表示される。
Container RegistryへのPush
Container RegistryよりもArtifact Registryの方が推奨されているみたいなので、使ってみるか?
一旦GCRの方でやってみて、すぐ変更できそうな気もするのであとでやる。
Container Registryにまずはpushしてみる。
push するイメージを取得する
イメージを Container Registry に追加する
イメージを Container Registry に push する
GCPコンソールにイメージが確認できました。
Artifact Registryのクイックスタートも見てみます。
Artifact Registryのクイックスタート
Dockerリポジトリを作成する
リポジトリが作成されたことを確認
→OK
認証を構成
pushするイメージを取得
イメージにレジストリ名をタグ付けする
イメージをArtifact Registryにpushする
GCPコンソールでイメージがpushされていることが確認できました!
qiitaの記事に戻る
imageのpushをします。
qiitaの記事ではContainer Registryを使っていましたが、Artifact Registryを使っていきます。
先にArtifact RegistryのGCPコンソールからリポジトリを作っておきます。
イメージにタグをつける
pushする
→ GCPコンソールから確認できました。
サービスの作成
うまくいかない。
イメージからCloud Runにデプロイをやってみると、以下のエラーメッセージが出てデプロイ失敗してしまいました。
localでは動いていたのに、、
これもやってみました。
これではちゃんと表示されていたので、なおさらおかしいですね。
以下のDockerfileとmain.goでやってみてもだめでした。
これを見て解決できるか?
ポートは問題ないと思うけどなぁ。
注: ARM ベースのマシンでコンテナ イメージを作成した場合、Cloud Run で意図したとおりに動作しないことがあります。この問題を解決するには、Cloud Build を使用してイメージをビルドします。
ARMベースじゃなくてApple M1 Proなんだけど、もしかしてこのあたりが問題なのか?
結論、以下で解決しました。
以下オプションを付けてbuildしてみる。
これでbuildしたらいけました!
Artifact RegistryのイメージからCloud Runにデプロイを選択して、リージョンはasia-northeast1にして、認証は「未認証の呼び出しを許可」にして「作成」
すると、サービスが作成されて、表示されたURLにアクセスすると、Hello World!の文字が!
くぅ〜これにて完結です!
このあたりは一回どっかできれいにまとめたいところ。
個人開発でのサーバー料金等の運用コストについて調査・検討
さて、そろそろ本格的にwebサービスの開発に着手していきたいと思いますが。
まず、全体の構成として
Go(gin) + Next.js
というのは決定しています。
そしてNext.jsをデプロイする先はvercelでほぼ決まりでしょう。
企業で本番環境で使うことはあまりないようなのですが、個人開発にとってはもってこいです。無料かつデプロイが超簡単なので。
herokuのノリですかね。
APIサーバー
AWSが親しみ慣れているが、せっかく個人開発なので、使ったことないサービスとか馴染みないサービスを使ってみたいところ。
なので、GCPを使おうと。
CloudRunが良さそう。
コンテナを簡単にいい感じにデプロイしたり、マネージしてくれたりするやつ?
料金も使わなければ安そう。
AWSのApp Runnerみたいなやつとのこと。
App Runnerも初見だった。
見ると2021年5月リリースなので、自分がエンジニア休憩していた時期だった。
友人にはCloud RunはAWSでいうECSみたいな感じって聞いていたが、それよりも簡単そう。VPCとかネットワークとかセキュリティグループとかの設定も皆無らしい。強い。
Cloud RunとCloud SQLについて書いてあった。
さらっとCloud SQLのミニマム料金書いてあった。12$くらいと。ちょっと調べてみても出てこなかったので助かる。
やはりCloud Runは良さそう。
ベストではなくてもベターではありそう。一回使ってみたいってのはあるので、これを採用する。
料金について
リクエストの処理中のみCPUを割り当てるサービスの料金
CPU: 毎月最初の 180,000 vCPU 秒は無料
メモリ: 毎月最初の 360,000 GiB 秒は無料
CPUが常に割り当てられるサービス
CPU: 46.656$/月(無料枠を加味していない)
メモリ: 5.184$/月(無料枠を加味していない)
これは後者は高すぎるので、前者でやる。
前者だと無料でできそう。
データベース
今回は普通にRDBMSでテーブルの設計をなんとなく考えていたので、となるとCloud Runを使う以上GCPでやるのがやりやすそうで、つまりはCloud SQLが有力です。
ネックになるのは料金。
個人開発者にとってこれは死活問題。
公式を見てみても、料金体系がよくわかりづらい。
これによると、(4年前の情報だけども)ミニマムで月額1500円弱くらい。
AWSのRDSでも東京リージョンでミニマムでも18$/月くらいかかるので、1500円だったら飲み込めるか。。
あるいは、無料で使える?データベースとしてFirebaseを選択するか。
しかし、今回GoとNext.jsが所見なので、ここにFirebaseも入ってくると流石に勉強期間が長過ぎてだれる恐れがあるので、やめときたい。
Firebaseそのものもそうだし、そもそものNoSQLでのデータのリレーションとかどうするのかとかもよくわかっていないため、かなり学習コストが高い。
それにGoを今後使っていきたいとして、殆どの企業では基本的にはRDBMSを使っている?と思うので、そのあたりにスムーズに入れるように、一旦今回はNo NoSQLでいきます。
料金について
vCPU: $39.19 per vCPU・月
メモリ: $6.64 per GB・月
ストレージ: HDD ストレージ容量: 1 GB あたり $0.117/月
ネットワーク: 内向き・外向きともにGoogleプロダクトなら大陸内は無料
インスタンス: 一番安いやつで$9.96/月
CPUとメモリ料金に加えて、インスタンス料金もかかるってこと?
インスタンスにはCPUとメモリは含まれていないの?
sharedの場合は、インスタンス料金だけでいけるような雰囲気がある。
これによると、1,646円。まあ許容できる。
あとはドメイン代くらいか。
全体で月2000円くらいで運用できそうかな。
がんばります。
Go言語のデータベース連携ライブラリの選定
ORMはsql-boiler
migrationはgoose
をGoをよく使っている友人に進められたので、調べてみます。
SQLBoilerについて
sql-boilerの他にORMライブラリは
・ GORM
・sqlx
などがある。
sql-boilerはマイグレーションの機能は入っていないが、それは他のマイグレーションライブラリを組み合わせれば良いので問題にはならないと。
コンパイル時にエラーが出るので、誤りにすぐ気がつくと。
公式ドキュメントを見てみます。
SQLBoiler は、データベーススキーマに合わせた Go ORM を生成するツールです。
(gorm/gorp のような) "コードファースト" とは対照的に、"データベースファースト" な ORM です。つまり、最初にデータベーススキーマを作成する必要があります。データベースのライフサイクルのこの部分を管理するために、sql-migrateやその他の移行ツールのようなものを使ってください。
gooseについて
・設定ファイルなし
・デフォルトのgooseバイナリはSQLファイルのみマイグレート可能
・Goのマイグレーション。
Go migrations: Go migrations 関数を goose バイナリからオンザフライでビルドすることはありません。
代わりに、独自のカスタム goose バイナリを作成し、Go マイグレーション関数を明示的に登録し、独自の *sql.DB 接続で複雑なマイグレーションを実行することができます。
Goマイグレーション関数では、*sql.Tx引数を使用すれば、SQLトランザクション内でコードを実行することができます。
・gooseのpkgはバイナリから切り離されています。
goose pkgはもうSQLドライバを登録しないので、コードベース内でのドライバのpanic()衝突がありません!
goose pkg は、もうベンダーへの依存を一切持ちません。
・デフォルトでタイムスタンプ付きのマイグレーションを使用しますが、開発工程ではタイムスタンプを使用し、実運用工程ではシーケンシャルバージョンを使用するハイブリッドアプローチを推奨します。
・allow-missing フラグを指定することで、missing (out-of-order) migrations をサポートします。ライブラリとして使用する場合は、関数オプション goose.WithAllowMissing() を Up, UpTo または UpByOne に指定してください。
・スキーマテーブルで追跡することなく、アドホックなマイグレーションを適用することをサポートします。移行が適用された後にデータベースをシードするのに便利です。no-versioning フラグまたは関数オプション goose.WithNoVersioning() を使用します。
DeepL翻訳より。
railsのActiveRecordのノリでいけるってことでしょうか?
そう信じてやっていきます。
リフレクションとは?
ちょいちょいリフレクションという単語が出てきているけど、聞いたことなかった。
実行時にわかるデータを使ってなにかの処理をさせたいときに使う、のかな?
なんとなくはわかったが、曖昧です。
概念はわかったような気がするけど、実際にこれをどう使うのか?なんで使いたいのかがよく理解できていないです。
これは一旦置いてお生きます。
データベース連携方針まとめ
色々比較検討した上で、決めていきたかったのですが、メリットデメリットを考えられるほどつらみとかを理解していないってのもあり、判断軸がないです。