ludwig125のブログ

頑張りすぎずに頑張る父

Ubuntu 18.04.2のgoのversionを最新にする

目的

go modを使いたかったのでgoを1.11以上にする必要があった せっかくなので最新にした

インストール方法参考

公式

環境

手順

ほとんどこのままできた How To Install Go 1.13 on Ubuntu 18.04 & 16.04 LTS – TecAdmin

Install

sudo apt-get update
sudo apt-get -y upgrade

最新のバージョンをこちらで確認して取ってくる

  • 現在の最新版は以下のリンクから確認できる

golang.org

wget https://dl.google.com/go/go1.12.7.linux-amd64.tar.gz

取ってきたgoを配置

sudo tar -C /usr/local -xzf  go1.12.7.linux-amd64.tar.gz

-C オプションをつけることで特定のディレクトリ先に解凍できる

Setup Go Environment

$ export GOROOT=/usr/local/go

GOPATHの設定

  • 自分の場合はGOPATHを以下にしている
$ export GOPATH=/home/$USER/go

$ echo $GOPATH
/home/ludwig125/go
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH

Verify Installation

$go version
go version go1.12.7 linux/amd64

set path

自分はzshを使っているので、.zshrcに以下を記載 これで次回から /usr/local/go 以下が読み込まれるようになる

# go のPATHを指定
export GOROOT=/usr/local/go
export GOPATH=/home/$USER/go
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH
  • 最初これを忘れてて、毎回起動時に /usr/bin 以下のgoが優先されて使われて困った

おわり

同一Lan内からLinuxの複数のファイルをまとめてダウンロードさせる

目的

家や会社などの、同じLan内の別のデバイスから複数のファイルをまとめてダウンロードできるようにする

具体例

他の人に複数の写真の共有をしたいときに、

  • メールなどで送るのめんどくさい
  • Dropboxなどのラウドサービスを使いたくない
  • USBメモリとかで受け渡ししたくない

そういうときに、Linuxなどpythonやzipコマンドが使える環境であれば簡単に(?)できる

環境

Ubuntu 18.04.2 LTS

python3 (python2でもいいはず)

手順

複数ファイルを以下のようにディレクトリにまとめて (ここではディレクトリ名は「0803」にしていたが任意)

[~/tmp/0803] $ls
P1020416.JPG*  P1020420.JPG*  P1020424.JPG*  P1020428.JPG*  P1020432.JPG*  P1020436.JPG*  P1020440.JPG*  P1020444.JPG*  P1020448.JPG*
P1020417.JPG*  P1020421.JPG*  P1020425.JPG*  P1020429.JPG*  P1020433.JPG*  P1020437.JPG*  P1020441.JPG*  P1020445.JPG*  P1020449.JPG*

以下でzip圧縮

zip -r 0803.zip 0803

圧縮したディレクトリと同じディレクトリ内でpythonでサーバを立てる

python3 -m http.server --cgi 8181

サーバを立てた状態であれば、同一Lan内のどのデバイスからでも以下で接続して0803.zipがダウンロードできる状態になっているはず(IPアドレスの確認方法は後述)

http://192.168.3.11:8181/

※ローカルIPアドレスを調べる方法はifconfig

  • 自分の例では192.168.3.11
$hostname -I
192.168.3.11 172.17.0.1 

参考:

ludwig125.hatenablog.com

GKEのチュートリアルでkubectlをインストールできなかった

cloud.google.com

こちらの方法に従ってkubectlをインストールしようとしたらエラーが出て失敗

gcloud components install kubectl
$ gcloud components install kubectl
 ERROR: (gcloud.components.install) 
You cannot perform this action because the Cloud SDK component manager 
is disabled for this installation. You can run the following command 
to achieve the same result for this installation: 

sudo apt-get install kubectl

sudo apt-get install kubectl も失敗した

$ sudo apt-get install kubectl
E: パッケージ 'kubectl ' にはインストール候補がありません

Kubernetes set-up on ubuntu on Google compute - Stack Overflow

ERROR: (gcloud.components.update) The component manager is disabled for this installation

It is a known issue on the Google Cloud SDK issue tracker : Issue 336: kubectl not installed by google-cloud-sdk debian package, and not installable

Unfortunately, it provides a poor experience for first timer testing kubernetes as it's hard to find a quick AND CLEAN step by step solution.

Here is one:

とあったので、以下のコマンドを逐次実行したことで解決した

sudo apt-get update
sudo apt-get remove google-cloud-sdk
curl https://sdk.cloud.google.com | bash
exec -l $SHELL
gcloud init
gcloud components list
gcloud components install kubectl
gcloud components list

注意:もともと入っているgcloud関連パッケージが消えてなくなるので、gcloud components listをして何が入っていたのかなど確認してから実行したほうがいい

go言語reflectメモ

reflect パッケージについてメモ

他記事:

ludwig125.hatenablog.com

reflectとは

  • reflectはGoでReflection機能を提供するためのパッケージ
  • このパッケージの関数を使うことで、引数の型や値を動的に取得できる

使い方1. (reflectで参照した変数の値を変更しないとき)

package main

import (
    "fmt"
    "reflect"
)

type Point struct {
    X int
    Y int
}

func main() {
    p := Point{10, 5} // 変数を定義
    rv := reflect.ValueOf(p)
    fmt.Printf("rv.Type = %v\n", rv.Type())  // 名前空間付きの型名
    fmt.Printf("rv.Kind= %v\n", rv.Kind()) // 格納リソース種別
    fmt.Printf("rv.Interface= %v\n", rv.Interface()) // interface{}としての実際の値

    xv := rv.Field(0) // rv内のX要素を取り出し
    fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示
}

実行結果

rv.Type = main.Point
rv.Kind= struct
rv.Interface= {10 5}
xv: 10

使い方2. (reflectで参照した変数の値を変更するとき)

  • 変数の値を変更するときは、SetInt(SetFloat, SetStringなど他にもいろいろ)を使う
  • ただし、変更するためには以下のようにValueOfにポインタを渡して、そのElemを取る必要がある
  • Elemはinterfaceの値もしくはポインタの参照先を返す

package main

import (
    "fmt"
    "reflect"
)

type Point struct {
    X int
    Y int
}

func main() {
    p := &Point{X: 10, Y: 5} // ポインタにすることによって編集可能にする
    rv := reflect.ValueOf(p).Elem() // Elemによってポインタが指している先の値を取得する
    fmt.Printf("rv.Type = %v\n", rv.Type())  // 名前空間付きの型名
    fmt.Printf("rv.Kind= %v\n", rv.Kind()) // 格納リソース種別
    fmt.Printf("rv.Interface= %v\n", rv.Interface()) // interface{}としての実際の値
    
    xv := rv.Field(0) // rv内のX要素を取り出し
    fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示    
    xv.SetInt(100) // intの100をX要素にセット
    fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
}

The Go Playground

実行結果

rv.Type = main.Point
rv.Kind= struct
rv.Interface= {10 5}
xv: 10
after SetInt xv: 100

【値を変更しようとしてpanicになる場合1】ポインタを渡さなかった場合

  • ポインタではなく値を変更しようとするとpanicになる
func main() {
    p := Point{X: 10, Y: 5}
    rv := reflect.ValueOf(p)
    xv := rv.Field(0)                             // rv内のX要素を取り出し
    fmt.Printf("xv: %d\n", xv.Int())              // intに変換して表示
    xv.SetInt(100)                               // intの100をX要素にセット => panic: reflect: reflect.Value.SetInt using unaddressable value
    fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
}

The Go Playground

実行結果

xv: 10
panic: reflect: reflect.Value.SetInt using unaddressable value

goroutine 1 [running]:
reflect.flag.mustBeAssignable(0x82, 0xf0a20)
    /usr/local/go/src/reflect/value.go:234 +0x180
reflect.Value.SetInt(0xf0460, 0x414020, 0x82, 0x7, 0x64, 0x0)
    /usr/local/go/src/reflect/value.go:1542 +0x40
main.main()
    /tmp/sandbox268672126/prog.go:18 +0x200

【値を変更しようとしてpanicになる場合2】Elemを使わなかった場合

  • Elemでreflect.Valueの指し示す先の値を取得していないとFieldメソッドを使った時にpanicになる
  • Field (再掲)
func main() {
    p := &Point{X: 10, Y: 5}
    rv := reflect.ValueOf(p)
    xv := rv.Field(0)                             // rv内のX要素を取り出し => panic: reflect: call of reflect.Value.Field on ptr Value
    fmt.Printf("xv: %d\n", xv.Int())              // intに変換して表示
    xv.SetInt(100)                                // intの100をX要素にセット
    fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
}

The Go Playground

実行結果

panic: reflect: call of reflect.Value.Field on ptr Value

goroutine 1 [running]:
reflect.Value.Field(0xe83c0, 0x414020, 0x16, 0x0, 0x40c120, 0x0, 0x90b80, 0x38f1)
    /usr/local/go/src/reflect/value.go:813 +0x160
main.main()
    /tmp/sandbox389676022/prog.go:16 +0xe0

値を変更できるか(SetXXXを使えるか)はCanSetを使うと判定できる

package main

import (
    "fmt"
    "reflect"
)

type Point struct {
    X int
    Y int
}

func main() {
    p := Point{X: 10, Y: 5}
    rv := reflect.ValueOf(p)
    if rv.CanSet() {
        fmt.Println("p CanSet: true")
        xv := rv.Field(0)                // rv内のX要素を取り出し
        fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示
        xv.SetInt(100)
        fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
    } else {
        fmt.Println("p CanSet: false")
    }
    pPtr := &Point{X: 10, Y: 5}
    rv = reflect.ValueOf(pPtr).Elem()
    if rv.CanSet() {
        fmt.Println("pPtr CanSet: true")
        xv := rv.Field(0)                // rv内のX要素を取り出し
        fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示
        xv.SetInt(100)
        fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示
    } else {
        fmt.Println("pPtr CanSet: true")
    }
}

The Go Playground

実行結果

p CanSet: false
pPtr CanSet: true
xv: 10
after SetInt xv: 100

使えそうな使用例

1. 構造体のフィールドを順に出力

package main

import (
    "fmt"
    "reflect"
)

type Point struct {
    X int
    Y int
    Z int
}

func (p Point) PrintFiled() {
    v := reflect.ValueOf(p)
    t := v.Type()
    for i := 0; i < t.NumField(); i++ {
        // 構造体のフィールド名、フィールドの値、フィールドの型を出力
        fmt.Printf("%s %d %T\n", t.Field(i).Name, v.Field(i).Interface(), v.Field(i).Interface())
    }
}

func main() {
    p1 := Point{X: 10, Y: 5}
    p1.PrintFiled()

    fmt.Println("----")

    p2 := Point{X: 100, Y: 50, Z: 7}
    p2.PrintFiled()
}

The Go Playground

実行結果

X 10 int
Y 5 int
Z 0 int
----
X 100 int
Y 50 int
Z 7 int

2. 構造体の型が何であってもinterface型のSliceにまとめて返す

  • どんな型であってもinterfaceのスライスにしたい場面があったので
  • 以下のように、toInterfaceSlice関数を定義して、PointとUserのInterfaceSliceメソッドの中でこの関数を呼ぶだけにすることで共通化できた
  • toInterfaceSlice関数の引数はinterface{}なのでどの構造体でも受け付けられる
package main

import (
    "fmt"
    "reflect"
)

func toInterfaceSlice(v interface{}) []interface{} {
    var vs []interface{}
    rv := reflect.ValueOf(v)
    rt := rv.Type()
    for i := 0; i < rt.NumField(); i++ {
        vs = append(vs, rv.Field(i).Interface())
    }
    return vs
}

type Point struct {
    X int
    Y int
    Z int
}

func (p Point) InterfaceSlice() []interface{} {
    return toInterfaceSlice(p)
}

type User struct {
    ID     int
    Name   string
    Height float64
    Weight float64
}
func (u User) InterfaceSlice() []interface{} {
    return toInterfaceSlice(u)
}

func main() {
    p := Point{X: 10, Y: 5, Z: 1}
    for _, v := range p.InterfaceSlice() {
        fmt.Printf("%v %T\n", v, v)
    }

    fmt.Println("----")

    u := User{ID: 1001, Name: "hoge", Height: 170.1, Weight: 60.45}
    for _, v := range u.InterfaceSlice() {
        fmt.Printf("%v %T\n", v, v)
    }
}

The Go Playground

実行結果

10 int
5 int
1 int
----
1001 int
hoge string
170.1 float64
60.45 float64

2-2. 関数に渡すパラメータが構造体のポインタの場合でも対応できるようにする

  • 上のtoInterfaceSlice関数で引数が構造体のポインタの場合には以下のようにElemを使って実体を取得するようにすれば、 引数が構造体の場合も、構造体のポインタの場合も両方対応できる
func toInterfaceSlice(v interface{}) []interface{} {
    var vs []interface{}

    rv := reflect.ValueOf(v)

    fmt.Printf("rv.Kind %v\n", rv.Kind())
    // パラメータvが構造体のポインタのときはElemでポインタの指している先の値を取得する
    if rv.Kind() == reflect.Ptr {
        // vが構造体のポインタの時はここを通る
        rv = reflect.ValueOf(v).Elem()
    }
    rt := rv.Type()
    for i := 0; i < rt.NumField(); i++ {
        vs = append(vs, rv.Field(i).Interface())
    }
    return vs
}

type Point struct {
    X int
    Y int
    Z int
}

func (p Point) InterfaceSlice() []interface{} {
    // toInterfaceSliceには構造体を渡す
    return toInterfaceSlice(p)
}

type User struct {
    ID     int
    Name   string
    Height float64
    Weight float64
}

func (u *User) InterfaceSlice() []interface{} {
    // toInterfaceSliceには構造体のポインタを渡す
    return toInterfaceSlice(u)
}

全体のコードは以下

The Go Playground

実行結果

rv.Kind struct
10 int
5 int
1 int
----
rv.Kind ptr
1001 int
hoge string
170.1 float64
60.45 float64

2-3. 構造体のフィールドに構造体を含んでいる場合にも対応できるようにする

  • 以下のように、構造体のフィールドに構造体を持つ場合(ネストしている場合)にも対応できるようにしたい
type GamePlayerInfo struct {
    User
    Point
}
  • 以下のように条件分岐して再帰を使ってみる
func toInterfaceSlice(v interface{}) []interface{} {
    var vs []interface{}

    rv := reflect.ValueOf(v)
    if rv.Kind() == reflect.Ptr {
        rv = reflect.ValueOf(v).Elem()
    }
    rt := rv.Type()
    for i := 0; i < rt.NumField(); i++ {
        if rv.Field(i).Kind() == reflect.Struct {
            // フィールドがstructの場合は再帰でinterfaceのSliceを取得して後ろにつなげる
            sl := toInterfaceSlice(rv.Field(i).Interface())
            vs = append(vs, sl...)
        } else {
            vs = append(vs, rv.Field(i).Interface())
        }
    }
    return vs
}

全体のコードは以下

The Go Playground

実行結果

10 int
5 int
1 int
----
1001 int
hoge string
170.1 float64
60.45 float64
----
1001 int
hoge string
170.1 float64
60.45 float64
10 int
5 int
1 int

2-4. 構造体のフィールドで〇〇メソッドを持つ場合はそれを使うようにする(おまけ)

  • 以下のように、Attribute変数(Enum)のように、Stringメソッドを持つ場合はそれで出力させる
  • MethodByName を使う
type Attribute int

const (
    Fire Attribute = iota
    Water
    Earth
    Air
)

// constのString変換メソッド
func (a Attribute) String() string {
    return []string{"Fire", "Water", "Earth", "Air"}[a]
}

type SpecialGamePlayerInfo struct {
    User
    Point
    Attribute
}

The Go Playground

参考

Go 言語 reflect チートシート - Qiita

Go言語の型とreflect - Qiita

Go言語におけるinterface{}とリフレクションを使ったパターン - Qiita

reflect-examples/README.md at master · a8m/reflect-examples · GitHub

Go言語におけるinterface{}とリフレクションを使ったパターン - Qiita

Learning to Use Go Reflection – Capital One Tech – Medium

CloudSQLを安くするために考えたこと

無料トライアル期間が終わったのでCloudSQLのマイグレーションをした話

安さを追求したわけではなく、自分なりの妥協点を探っただけ

事象

2019/5/22 CloudSQLに接続できなくなった

$ gcloud sql connect myfinance --user=root
ERROR: (gcloud.sql.connect) HTTPError 409: The instance or operation is not in an appropriate state to handle the request.

GoogleAppEngineのログには以下が出力されていた

failed to insert table: daily, err: driver: bad connection, query: 

原因

Google Cloud PlatformのUIの一番上を見ると無料トライアル期間が終わったとを通知していた

無料トライアルは終了しました

上のUIの右の「アップグレード」ボタンを押すと以下のように表示された

アカウントのアップグレード

これを承諾するまえに、そもそも無料トライアル期間中はいくらくらい使っていたのか確認してみた

これからかかるはずの料金の確認

レポートを見てみる

レポートの見方 - 請求レポートによる費用傾向の表示  |  Cloud Billing のドキュメント  |  Google Cloud

「お支払い」の「レポート」を見てみた

image

レポートの、先月分をプロダクト単位で見てみる - グループ条件を変更

SKU単位で見てみると使用しているサービスの内訳がわかる

image

SKU プロダクト SKU ID 使用 クレジット適用前の費用 プロモーション 割引 クレジット適用後の費用
DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) Cloud SQL 5356-0253-5FBD 720 hour ¥6,996 ¥-6,996 ¥0
Storage PD SSD for DB in Japan Cloud SQL 9D66-0506-E274 10 gibibyte month ¥244 ¥-244 ¥0

特にこれ - DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) - クレジット適用前の費用:¥6,996

・・・なにこの値段

SKUって?

単品管理 - Wikipedia

SKUは最小管理単位 (Stock Keeping Unit) の略。

Google Cloud PlatformにはSKUの意味について説明がなかったのでたぶんこれのことだと思う

毎月の請求について  |  Cloud Billing のドキュメント  |  Google Cloud

SKU ID サービスが使用するリソースの ID。SKU の詳細な一覧については、GCP SKU をご覧ください。

GCP SKU一覧

SKUs  |  Google Cloud

この単位で料金を決めているらしい

この金額が正当なのか確認

上で表示したレポートのSKUの一覧を見ると、 DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) に一番金がかかっているらしい

  • ¥6,996

image

この「DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount)」を上の公式の表「GCP SKU一覧」から探すとあった image

DB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) - 0.088 USD per hour

この0.088を4月中フル稼働した場合(720時間)で、現在の円ドルレート 約110円を当てはめると、 0.088720110=6969.6 となって上とほぼ等しい

with 30% promotional discount

この「with 30% promotional discount」というのは、 「継続利用割引」というサービスらしい

Google Compute Engine の料金  |  Compute Engine ドキュメント  |  Google Cloud

継続利用割引は、使用する vCPU とメモリ量ごとに計算されます。Compute Engine では、1 か月の 25% を超える期間にわたり vCPU またはメモリ量がインスタンスで使用されると、そのリソースの使用が 1 秒延びるごとに割引が自動的に適用されます。 使用時間が増えるほど割引率は高くなり、1 か月ずっと稼働するインスタンスでは vCPU とメモリの費用の最大 30% の正味割引を受けることができます。

CloudSQLの料金体系について確認する

Cloud SQL の料金  |  Cloud SQL ドキュメント  |  Google Cloud

自分が使っているのはCloudSQLのMysql 第 2 世代なのでそれを見る

MySQL 第 2 世代の料金 第 2 世代の料金は、次の料金で構成されます。 インスタンスの料金 ストレージの料金 ネットワークの料金

mysql 2ndの料金

東京(asia-northeast1)を見ると、

マシンタイプ 仮想 CPU 数 RAM(GB) 最大ストレージ容量 最大接続数 料金(米ドル) 継続利用価格(米ドル)
db-n1-standard-1 1 3.75 10,230 GB 4,000 $0.13 $0.09

となっていた。

image

これは上のDB standard Intel N1 1 VCPU running in Japan (with 30% promotional discount) の 0.088という数字と一致する

なるほど・・・高い

インスタンスの設定

インスタンスの設定  |  Cloud SQL ドキュメント  |  Google Cloud

Google Cloud SQL の第 1 世代および第 2 世代のインスタンスで使用できるすべての設定について

こちらに詳しく書いてある

どうして720時間(一月休みなく動かし続けるとこの時間)使っていることになっているのか?

これが一番疑問だった

自分のサービスはCronで一日せいぜい2時間くらいしか動いていないのにどうして休みなく稼働していることになっているのか?

調べてみると、以下の通り明示的にDBを停止させない限り、ずーっと稼働していて、その分料金もかかるらしい

Mysqlの第 2 世代の特徴

第 2 世代機能  |  Cloud SQL ドキュメント  |  Google Cloud

料金の違い Cloud SQL 第 2 世代では、従量制の料金パッケージが提供されていません。インスタンスの料金は、マシンタイプによって決まります。分単位の課金と継続利用割引の導入により、Cloud SQL 第 2 世代では多くのワークロードで費用対効果を高めることができます。詳しくは、料金のページをご覧ください。

アクティベーションポリシー

インスタンスの設定  |  Cloud SQL ドキュメント  |  Google Cloud

アクティベーション ポリシー 第 2 世代インスタンスの場合、アクティベーション ポリシーはインスタンスを起動または停止するためにのみ使用されます。アクティベーション ポリシーを [常にオン] に設定するとインスタンスが起動し、[オフ] に設定するとインスタンスが停止します。

つまり、オフにしない限り利用し続けている状態となるらしい

  • 第1世代にはON DEMAND というポリシーがあって、リクエストが来たら起動する(立ち上がりに時間かかるけど)という仕組みがあったらしいが、それは第2世代にはない

インスタンスの起動、停止はどうすればいいのか

インスタンスの起動、停止、再起動  |  Cloud SQL  |  Google Cloud

ブラウザまたはgcloudコマンドで起動・停止できるらしい

料金を安くできないか

以上より、料金を安く抑えてCloudSQLを使うためには以下の3つが考えられそう

  1. インスタンスのリージョンを安いところにする
  2. マシンを一番安いものにする
  3. 使わないときはインスタンスを止める

3番目については自動でgcloudコマンドを実行できるようにそのうちしたい

一番安いリージョンの一番安いマシンを調べる

https://cloud.google.com/sql/pricing?hl=ja

をいろいろ見た結果、 アメリカなどのリージョンのdb-f1-microマシンが一番安かったのでこれを選択 なんでも良かったので、アイオワにする

image

リージョン マシンタイプ 仮想 CPU 数 RAM(GB) 最大ストレージ容量 最大接続数 料金(米ドル) 継続利用価格(米ドル)
アイオワ db-f1-micro* 共有 0.6 3,062 GB 250 $0.0150 $0.0105
東京 db-n1-standard-1 1 3.75 10,230 GB 4,000 $0.1255 $0.0878

アイオワのdb-f1-micro は東京のdb-n1-standard-1の1/8以下

継続利用価格(米ドル)では$0.0105 / hour

※この料金は一時間あたりの利用料金のことらしい

Google Cloud Platform 料金計算ツール  |  Google Cloud Platform  |  Google Cloud - このツールで見積もりもできる

自分のサービスはSLAがめちゃめちゃ低く、レイテンシーはほとんど気にしない。データサイズも数十GBなので、 db-f1-microで十分そう

想定費用を計算してみた

リージョン×マシンタイプ 料金(24時間×31日×110円 ※ 110は現在のレート)
アイオワ db-f1-micro 0.01052431*110=859.32円
東京 db-n1-standard-1o 0.08782431*110=7185.552円

インスタンスの他にストレージとネットワークの料金が別にかかるが、一旦これだけ考える

もとの金額に比べれば、趣味で使う金額としてだいぶ現実的になった

ストレージについて

ストレージはSSDのほうが当然高い

でもこれくらいなら許容しようかな(あとで変えられるのでSSDを選ぶ) - $0.17 per GB/month for SSD storage capacity - $0.09 per GB/month for HDD storage capacity

SKUの表で料金を確認

https://cloud.google.com/skus/?currency=JPY&filter=micro image

CloudSQLの表と名前が一対一対応していないのでわかりにくいけど、おそらくこれが対応するSKUとその金額だと思える

DB generic Micro instance with burstable CPU running in Americas (with 30% promotional discount) - 0.0105 USD per hour

新規DBの作成

アップグレード

最初のアップグレードボタンを押して使えるようにする

インスタンスの作成

https://console.cloud.google.com/sql/instances から、アイオワリージョンのdb-f1-microを新しく作る

image

で「インスタンスを作成」

image

Mysqlを選択

image

任意のインスタンスIDとパスワードを設定する

このときに下の「設定オプションを表示」を押すと、マシンタイプなどを細かく設定できる - リージョンはあとから変えられない

※あとでも変更できるけど、何も設定しないとdb-n1-standard-1になってしまうので超注意!!

インスタンスの編集

上の、「設定オプションを表示」をいじらなくても、あとからマシンタイプなどは変えられる

Google Cloud PlatformSQLインスタンスが見られるので、その画面の中の「設定」→「設定を編集」から設定を変更できる

image

マシンタイプとストレージの設定

マシンタイプとストレージの設定を編集

変更前

マシンタイプとストレージ

変更後

image

  • マシンタイプ:db-f1-micro
  • ストレージの種類:SSD
  • ストレージ容量 :最低の10GB
  • ストレージの自動増量を有効化:チェックした

自動バックアップの有効化

しない - とりあえず、自分で定期的に頑張ることにする

image

新旧DBデータ移行汎用手順

0. 準備

Cloud SQL for MySQL の使用  |  Go の App Engine スタンダード環境  |  Google Cloud

Cloud SQL for MySQL のクイックスタート  |  Cloud SQL for MySQL  |  Google Cloud

これに従ってcloud_sql_proxyをローカルマシンに入れておく

事前に新旧DBのインスタンス接続名を調べておく

Google Cloud Platform の左上のボタンを押してからSQLを選んで、

image

対象のインスタンスIDを選択して、 「このインスタンスに接続」->「インスタンス接続名」から取得できる

このインスタンスに接続

1. 旧DBへのプロキシサーバ立ち上げ

ローカルの端末で以下を実施

$ cloud_sql_proxy -instances=<インスタンス接続名>=tcp:3307

2. 旧DBのテーブルをDump(Export)

別の端末を開いてTableデータをDump

$mysqldump -u root  -p --host 127.0.0.1 --port 3307 <Database名> <テーブル名> > dumpdata.sql
  • Database名: show databases; で出てくるなかの対象のDatabase

3. 新DBのテーブルへImport

新DBへのプロキシサーバ立ち上げ

先程までの旧DBに繋がっていたプロキシサーバは止めて、ローカルの端末で以下を実施

$ cloud_sql_proxy -instances=<新DBのインスタンス接続名>=tcp:3307

4. 新DBでDatabaseを作っておく

別の端末で新DBに接続

$mysql -u root -p --host 127.0.0.1 --port 3307


MySQL [(none)]> CREATE DATABASE <Database名>;

5. 新DBのテーブルへImport

別の端末を開いてTableデータをImport

$mysql -u root -p --host 127.0.0.1 --port 3307 <Database名> < dumpdata.sql

GoogleHomeまたはAndroidに話しかけてスプレッドシートにメモを取る

概要

GoogleHomeやAndroidに話しかけるだけでスプレッドシートにメモを取ることができる

ちょっとしたメモを取るのに便利

ludwig125.hatenablog.com

こちらとほぼ同じ方法で作った

手順

これだけ 1.スプレッドシートを用意する 2.スプレッドシートにGoogleAppScriptを追加する 3.IFTTTを登録する

1.スプレッドシートシートを用意する

f:id:ludwig125:20190513153022p:plain

新しいスプレッドシートを作成 → 空白を選ぶ

f:id:ludwig125:20190513153111p:plain

ここでは適当に「test」という名前のスプレッドシートにしたが何でもいい

2.スプレッドシートにGoogleAppScriptを追加する

GoogleAppScriptを作成

f:id:ludwig125:20190513153157p:plain

上のタブの「ツール」→ 「スクリプト エディタ」を選択

適当に「test-gas」というプロジェクト名にするが何でもいい

f:id:ludwig125:20190513153347p:plain

コード.gsに以下を記入する

function addDate() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var lastRow = sheet.getLastRow();
  var date = new Date();
  var unixTimestamp = Math.round( date.getTime() / 1000 );
  sheet.getRange(lastRow, 1).setValue(date);
}

「lastRow, 1」にすると縦一列目に入力時刻が記載される

f:id:ludwig125:20190513153517p:plain

トリガーを追加

f:id:ludwig125:20190513153539p:plain

コード.gsの上の部分の時計のアイコンを選択

f:id:ludwig125:20190513153614p:plain

「新しいトリガーを作成します。」をクリック

必要な部分を選択 - 実行する関数を選択: addDate - 実行するデプロイを選択:Head ←これしかなかった - イベントのソースを選択:スプレッドシートから - イベントの種類を選択:変更時 - エラー通知設定:1週間おきに通知を受け取る ← 好みで変えていい

f:id:ludwig125:20190513153933p:plain

記載したら保存

「アカウントの選択」でGmailIDを選択

f:id:ludwig125:20190513154037p:plain

「このアプリは確認されていません」と出るが、無視して詳細を選択して、 「~~(安全ではないページ)に移動」をクリックする

f:id:ludwig125:20190513154142p:plain

「test-gas が Google アカウントへのアクセスをリクエストしています」 の下の「許可」を押す

f:id:ludwig125:20190513154417p:plain

ここまでの動作確認

ここまでやったら最初のtestシートに戻る

f:id:ludwig125:20190513154332p:plain

Bより右の列に適当に入力してEnterキーを押すと以下のようにA列に日付が自動で出力されるはず

f:id:ludwig125:20190513154810p:plain

これだけでもいいが、時刻までシートに表示させたいので、表示形式を変える

f:id:ludwig125:20190513154914p:plain

A列目を全部選択して、「表示形式」→ 「数値」→「日時」を選択

f:id:ludwig125:20190513155100p:plain

またA列目を全部選択して右クリックから「条件付き書式」を選ぶと、以降A列目だけ色がつくので見やすい(やらなくてもいい)

3.IFTTTを登録する

IFTTTのトップページにいって「My Applets」を選択

ifttt.com

f:id:ludwig125:20190513162254p:plain

New Appletを選択

this

f:id:ludwig125:20190513162322p:plain

f:id:ludwig125:20190513162349p:plain

Google Assistantを選択

Say a phrase with a text ingredient を選択

f:id:ludwig125:20190513162542p:plain

「テスト $」や「test $」など発音しやすい(かつGoogleAssistantが他の言葉と区別しやすい)言葉を選択

Create triggerを押す

that

f:id:ludwig125:20190513162723p:plain

Google Sheetsを選択

Add row to spreadsheet を選択して以下のようにする

f:id:ludwig125:20190513162843p:plain

f:id:ludwig125:20190513162912p:plain

もしGoogleAppScriptおよびトリガーを設定せずに、左の列にCreatedAtを選択するとGoogleHome経由で入力すると「May 14, 2019 at 08:48PM」のような形式でスプレッドシートに記載される この機能は昔は機能していなかった気がするので、GoogleAppScriptをわざわざ作ったんだけど。。 ただ、日付の形式は変えられない。 もし形式がこれでよければスプレッドシート側のスクリプトは不要そう f:id:ludwig125:20190515064221p:plain

動作確認

GoogleHomeまたはAndroidで「OK Google テスト 123」

f:id:ludwig125:20190515065204p:plain

なぜかGoogleHome経由からだと時刻が表示されない。 でもセルを見れば時刻がわかるからいいかな。。

GoogleHome経由でスプレッドシートに記入されないとき

Google ドライブ - 1 か所であらゆるファイルを保管 Google Driveを見て、以下のthatで定義したGoogleHomeのフォルダに対象のシートが入っているか確認する

f:id:ludwig125:20190515065311p:plain

別のフォルダの同名のシートに書き込まれていることがあった

f:id:ludwig125:20190515063015p:plain

GoogleAppEngine(GAE)でgoroutineを使う

GoogleAppEngineでgoroutineを使う

以下に記載されている通り、GoogleAppEngineでは並列処理はサポートしていない。

Go Runtime Environment  |  App Engine standard environment for Go  |  Google Cloud

App Engine の Go ランタイム環境は、goroutine を完全にサポートしていますが、並列実行はサポートしていません。goroutine は単一のオペレーティング システム スレッドにスケジュールされます。このシングルスレッド制限は、今後のバージョンで解除される予定です。特定のインスタンスで複数のリクエストを同時に処理することができます。これは、1 つのリクエストで Cloud Datastore API の呼び出しを待っている間に、同じインスタンスで別のリクエストを処理できることを意味します。

しかし、並行処理はできる

これは、同時に複数の処理を実行すること(並列処理)はできないが、APIのレスポンスなどを待つ間に別の処理をすること(並行処理)はできるということを意味する

【脱線】並行性と並列性の違い

www.oreilly.co.jp

「Go言語による並行処理」ではこの違いを以下の一文で表している

並行性はコードの性質を指し、並列性は動作しているプログラムの性質を指します。

ピンと来ない表現・・・

検証

検証用のコード

以下のような3つのHandlerを作成して、それぞれの挙動を確認する - testHandler - testGoroutineHandler - testGoroutineHandler2

挙動の違いが分かりやすいように、time.Sleep(3 * time.Second)を入れている

このコードは以下の「複数のgoroutineの結果の取得」に書いたものを使っている go言語の並行処理 - ludwig125のブログ

package main                                                                                   

import (
    "fmt"
    "net/http"
    "sync"
    "time"

    "google.golang.org/appengine" // Required external App Engine library
    "google.golang.org/appengine/log"
    "google.golang.org/appengine/urlfetch" // 外部にhttpするため(http.GetはGAEでは使えない)
)

func main() {
    http.HandleFunc("/test", testHandler)
    http.HandleFunc("/test_goroutine", testGoroutineHandler)
    http.HandleFunc("/test_goroutine2", testGoroutineHandler2)
    appengine.Main() // Starts the server to receive requests
}

func testHandler(w http.ResponseWriter, r *http.Request) {
    // GAE log
    ctx := appengine.NewContext(r)
    client := urlfetch.Client(ctx)

    urls := []string{"https://www.google.com", "https://badhost", "https://www.yahoo.co.jp/"}
    checkStatus := func(urls ...string) []*http.Response {
        var response []*http.Response
        for _, url := range urls {
            log.Infof(ctx, "client.Get '%s'", url)
            resp, err := client.Get(url)
            if err != nil {
                log.Errorf(ctx, "%v", err)
                continue
            }
            response = append(response, resp)
            // 関数の実行にかかる時間を遅らせる
            log.Infof(ctx, "sleep 3 sec")
            time.Sleep(3 * time.Second)
        }
        return response
    }

    for _, response := range checkStatus(urls...) {
        log.Infof(ctx, "checkStatus: status: %s", response.Status)
    }                                                                                                                                                                 
}

func testGoroutineHandler(w http.ResponseWriter, r *http.Request) {
    // GAE log
    ctx := appengine.NewContext(r)
    client := urlfetch.Client(ctx)

    type Result struct {
        Error    error
        Response *http.Response
    }
    checkStatus := func(
        done <-chan interface{},
        urls ...string,
    ) <-chan Result {
        resultChan := make(chan Result)
        go func() {
            defer close(resultChan)
            for _, url := range urls {
                log.Infof(ctx, "client.Get '%s'", url)
                resp, err := client.Get(url)
                // 関数の実行にかかる時間を遅らせる
                log.Infof(ctx, "sleep 3 sec")
                time.Sleep(3 * time.Second)
                result := Result{Error: err, Response: resp}
                select {
                case <-done:
                    return
                case resultChan <- result:
                }
            }
        }()
        return resultChan
    }

    done := make(chan interface{})                                                                                             
    defer close(done)

    urls := []string{"https://www.google.com", "https://badhost", "https://www.yahoo.co.jp/"}
    for result := range checkStatus(done, urls...) {
        if result.Error != nil {
            log.Infof(ctx, "error: %v", result.Error)
            continue
        }
        log.Infof(ctx, "checkStatus: status: %s", result.Response.Status)
    }
}

    urls := []string{"https://www.google.com", "https://badhost", "https://www.yahoo.co.jp/"}
    for result := range checkStatus(done, urls...) {
        if result.Error != nil {
            log.Infof(ctx, "error: %v", result.Error)
            continue
        }
        //log.Infof(ctx, "%s", formatStatus(result.Response.Status))
        log.Infof(ctx, "checkStatus: status: %s", result.Response.Status)
    }
}

func testGoroutineHandler2(w http.ResponseWriter, r *http.Request) {
    // GAE log
    ctx := appengine.NewContext(r)
    client := urlfetch.Client(ctx)

    type Result struct {
        Error    error
        Response *http.Response
    }
    checkStatus := func(
        done <-chan interface{},
        urls []string,
    ) <-chan Result {
        resultChan := make(chan Result, len(urls))
        wg := new(sync.WaitGroup)

        defer close(resultChan)
        for _, url := range urls {
            wg.Add(1)
            go func(url string) {
                defer wg.Done()
                log.Infof(ctx, "client.Get '%s'", url)
                resp, err := client.Get(url)
                // 関数の実行にかかる時間を遅らせる
                log.Infof(ctx, "sleep 3 sec")
                time.Sleep(3 * time.Second)
                select {
                case <-done:
                    return
                case resultChan <- Result{Error: err, Response: resp}:
                }
            }(url)
        }
        wg.Wait()
        return resultChan                                                                                                             
    }

    done := make(chan interface{})
    defer close(done)
    urls := []string{"https://www.google.com", "https://badhost", "https://www.yahoo.co.jp/"}
    for result := range checkStatus(done, urls) {
        if result.Error != nil {
            log.Infof(ctx, "error: %v", result.Error)
            continue
        }
        log.Infof(ctx, "checkStatus: status: %s", result.Response.Status)
    }
}

実行して結果を確認

GoogleAppEngineの左側からトレースを選ぶと詳細がみられる

f:id:ludwig125:20190513065411p:plain

またトレース画面の右側のログの「表示」をクリックするとログがみられる

1.test(testHandler)

トレース f:id:ludwig125:20190513065517p:plain - 3つのURLの処理を3秒おきに実行していることがわかる

ログ f:id:ludwig125:20190513065641p:plain

2.test_goroutine(testGoroutineHandler)

  • goroutineを使っているが、URLは3つを順番に処理している
  • 3つそれぞれで3秒ごとのSleepを入れているため、全体の実行時間は9秒ちょっとになっている

トレース

f:id:ludwig125:20190513065853p:plain

ログ

f:id:ludwig125:20190513065945p:plain

3.test_goroutine2(testGoroutineHandler2)

  • (ほぼ)同時に3つのgoroutineを実行している
  • 「並列実行はサポートしていません」と公式に書いてあるので、おそらく全く同時ではないはずだが、これ見るとほぼ同時に見える。。
  • ともかく、並行で3つの処理を行うようにしたことで、全体としての処理時間が3秒ちょっとになった

トレース

f:id:ludwig125:20190513070119p:plain

ログ

f:id:ludwig125:20190513070141p:plain