DRYな備忘録

Don't Repeat Yourself.

【Go言語】Ctrl+cなどによるSIGINTの捕捉とdeferの実行

問題

deferを使って後処理をしたい場合に、プロセスがCtrl+cなどSIGINTで中断されるとdeferしたものが発火しない。プロセス自体が中断されるのであたりまえなんだけども。

問題の再現

package main

import (
    "fmt"
    "time"
)

func main() {

    defer teardown()

    hoge()

    fmt.Println("終了")

}

func hoge() {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
        time.Sleep(1 * time.Second)
    }
}

func teardown() {
    fmt.Println("データのあとかたづけ")
}

上記の実行

% go run main.go
0
1
2
3
4
終了
データのあとかたづけ
%

SIGINTの場合

% go run main.go
0
1
2
3
^Csignal: interrupt
%
# 「データのあとかたづけ」が発火しない

SIGINTの捕捉

package main

import (
    "fmt"
    "os"
    "os/signal"
    "time"
)

func main() {

    defer teardown()

    // {{{ ここから
    // SIGINTを待ち受けるchanを作成
    c := make(chan os.Signal, 1)
    // それを登録
    signal.Notify(c, os.Interrupt)
    // chanからの通知を受けるgoroutineを起動
    go func() {
        for sig := range c {
            fmt.Println("シグナル来た", sig)
            close(c)
            // SIGINTをchanで吸収しちゃってるので、
            // 明示的にExitする必要がある。
            os.Exit(130)
        }
    }()
    // }}} ここまで追加

    hoge()

    fmt.Println("終了")

}

func hoge() {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
        time.Sleep(1 * time.Second)
    }
}

func teardown() {
    fmt.Println("データのあとかたづけ")
}

os.Exitを使い明示的にプロセスを終了しており、このままではdeferしたteardownは呼ばれないので、以下の行を追加した。

                for sig := range c {
                        fmt.Println("シグナル来た", sig)
                        close(c)
+                       teardown()
                        // SIGINTをchanで吸収しちゃってるので、
                        // 明示的にExitする必要がある。
                        os.Exit(130)

実行してCtrl+cすると、

% go run main.go
0
1
2
^Cシグナル来た interrupt
データのあとかたづけ
exit status 130
%

となっていい感じ。

deferのためのリファクタリング

teardownの呼び出しが2箇所に書かなきゃいけないのはあまり筋がよくないので、defer一発でどうにかしたほうがシュッとすると思う。「hogeが終わった」というメッセージを伝達するdoneというようなチャンネルを経由して、SIGINTを受けるチャンネルと並列で扱うのがいいんではないか。終了コードが0になってしまうが、まあいっか。

package main

import (
    "fmt"
    "os"
    "os/signal"
    "time"
)

func main() {

    defer teardown()

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)

    done := make(chan error, 1)
    go hoge(done)

    select {
    case sig := <-c:
        fmt.Println("シグナル来た:", sig)
        /*
         teardown中に再度SIGINTが来る場合を考慮し、
         send on closed channelのpanicを避ける。
       */
        // close(c)
        return
    case err := <-done:
        fmt.Println("hogeの終了:", err)
    }

    fmt.Println("終了")

}

func hoge(done chan<- error) {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
        time.Sleep(1 * time.Second)
    }
    done <- nil
    close(done)
}

func teardown() {
    fmt.Println("データのあとかたづけ")
}

非常にGoっぽいコードになった。

参考

DRYな備忘録として

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

【メモ】aws-sdk-goを使ったSecurityGroupの作成

tl;dr

docs.aws.amazon.com

動くコード

package main

import (
    "fmt"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/ec2"
)

func main() {

    sess := session.Must(session.NewSessionWithOptions(session.Options{
        SharedConfigState: session.SharedConfigEnable,
        Config:            aws.Config{Region: aws.String("us-west-1")},
    }))
    client := ec2.New(sess)

    // 名前がコンフリクトするので同名のものがあれば削除しておく
    _, err := client.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{
        GroupName: aws.String("test-sdk-go"),
    })
    fmt.Println("DELETE:", err)

    // SGの作成
    group, err := client.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{
        GroupName:   aws.String("test-sdk-go"),
        Description: aws.String("Foo Bar Baz"),
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("GROUP: %v\n", group)

    // ルールの追加
    _, err = client.AuthorizeSecurityGroupIngress(&ec2.AuthorizeSecurityGroupIngressInput{
        GroupId: group.GroupId,
        IpPermissions: []*ec2.IpPermission{
            &ec2.IpPermission{
                IpRanges:   []*ec2.IpRange{&ec2.IpRange{CidrIp: aws.String("0.0.0.0/0")}},
                IpProtocol: aws.String("tcp"),
                FromPort:   aws.Int64(22),
                ToPort:     aws.Int64(22),
            },
        },
    })
    fmt.Println(err)
}

確認

このSGを持ったEC2インスタンスを立ち上げ、sshの疎通が確認できた。

ストレージサービスのBucket存在確認メモ

S3

s3://hoge

aws s3api wait bucket-exists --bucket hoge
echo $? # 255
  • 存在するが権限が無い
    • Waiter BucketExists failed: Forbidden
  • 謎ケース(要調査: このへん?
    • Waiter BucketExists failed: Bad Request
    • 再現
      • aws s3api wait bucket-exists --bucket xxxyyyzzz
  • 存在しない
    • Waiter BucketExists failed: Max attempts exceeded ← 遅い!

Cloud Storage

gs://hoge

gsutil ls hoge
echo $? # 1
  • 存在するが権限が無い
    • AccessDeniedException: 403 otiai10@gmail.com does not have storage.objects.list access to xxxyyyzzz.
  • 存在しない
    • BucketNotFoundException: 404 gs://wwwxxxyyyzzz bucket does not exist. 早い

// TODO

ほかの

【Go言語】bufio.Scannerの静かな突然死に3営業日ハマった

tl;dr

  • bufio.ScannerScanが終わってもErrはちゃんと見ましょう。以上。

問題

継続的に書き込みのあるTCP接続からの読み込みを、bufio.Scannerをつかって華麗に逐次読み込みしていた。が、どうやらそれが静かに切断され、クライアント(こちら)側のローカルでの処理が先走っているように見える挙動があった。

res, err := ExecRemoteLongScript()
defer res.Body.Close()

scanner := bufio.NewScanner(res.Body)
for scanner.Scan() {
    // do something for scanner.Bytes()
}

fmt.Println("done")

みたいなので、RemoteScript(概念)が完全に終了していないのに、クライアント(こちら)側でfmt.Println("done")に到達してしまっている。

再現と原因

すごく色々なレイヤーのある話だったので、色々調査したんですが、最終的に再現できたのはこれでした。

package main

import (
    "bufio"
    "fmt"
    "io"
)

func dummyExecRemoteScript() io.Reader {
    r, w := io.Pipe()
    text := "Test"
    go func() {
        for i := 0; i < 20; i++ {
            text += text
            w.Write([]byte(text + "\n"))
        }
        r.Close()
    }()
    return r
}

func main() {

    body := dummyExecRemoteScript()
    scanner := bufio.NewScanner(body)

    i := 0
    for scanner.Scan() {
        fmt.Println(i, len(scanner.Bytes()))
        i++
    }

    fmt.Println("done")
}

0から19までナンバリングされたレスポンス断片が出力されるはずなんですが、なぜか12番までしか出力されません https://play.golang.org/p/DDeu8kfrU5D。なんでやろなーと思って( ゚д゚)ハッ!っと気づいたのが、bufio.Scanner.Errです。

ということで、

   for scanner.Scan() {
        fmt.Println(i, len(scanner.Bytes()))
        i++
    }
+   fmt.Println("Err", scanner.Err())

    fmt.Println("done")

としたら、これがビンゴでした。 https://play.golang.org/p/HlR7SpCTSkc

0 8
1 16
2 32
3 64
4 128
5 256
6 512
7 1024
8 2048
9 4096
10 8192
11 16384
12 32768
Err bufio.Scanner: token too long
done

Scanning stops unrecoverably at EOF, the first I/O error, or a token too large to fit in the buffer. When a scan stops, the reader may have advanced arbitrarily far past the last token. Programs that need more control over error handling or large tokens, or must run sequential scans on a reader, should use bufio.Reader instead.
https://golang.org/pkg/bufio/#Scanner

解決

上記の通り、bufio.Scannerではなく、bufio.Readerを使って同じ内容を書き直します。

func main() {

    body := dummyExecRemoteScript()
    reader := bufio.NewReader(body)

    i := 0
    for {
        b, err := reader.ReadBytes('\n')
        if err != nil {
            if err == io.EOF || err == io.ErrClosedPipe {
                break
            }
            panic(err)
        }
        fmt.Println(i, len(b))
        i++
    }

    fmt.Println("done")
}

https://play.golang.org/p/vmctCOfK9nM

まとめ

  • bufio.Scannerは便利だけど、Scanが終わってもErrは必ず見るようにしましょう

自戒

DRYな備忘録として

追記 ⛳

コードゴルフできるところみつけたんでちょっと変えた

- i := 0
-  for {
+   for i := 0; ; i++ {
        b, err := reader.ReadBytes('\n')
        if err != nil {
            if err == io.EOF || err == io.ErrClosedPipe {
                break
            }
            panic(err)
        }
        fmt.Println(i, len(b))
-      i++
    }

みんなのGo言語[現場で使える実践テクニック]

みんなのGo言語[現場で使える実践テクニック]

Webプログラマから見た「CWL」の功績と罪過

このエントリは、CWL Advent Calendar 2017 - Qiitaの25日目です。

背景

f:id:otiai10:20171225123808p:plain

誰がエモ芸人や

CWL is 何?

異なる処理系の間で同一のワークフローの実行を可能にするために、ワークフローの内容を記述し、実行系の間で交換するための標準形式の策定を目的として Common Workflow Language (CWL) プロジェクトが発足した。

すげーざっくり言うと、バイオインフォのゲノム解析の手順書を統一規格で書こうぜ、という感じのブツです。

実際どうやって使うねん

% cwltool 1st-tool.cwl echo-job.yml

サンプルで、1st-tool.cwlと書かれているものが、いわゆるワークフローの定義であり、echo-job.ymlというのはパラメータとかサンプルとか呼ばれたりするらしいです。これ、なんで分割されているかというと、

世界のどこかで、とっても良い感じのワークフローを提唱したひとがいたとして、

f:id:otiai10:20171225131640p:plain

同じワークフローで別のサンプルで試してみたいと思うひとが出てきます。

f:id:otiai10:20171225132547p:plain

ということで、ワークフローの記述実際のパラメータは分割されている必要があります。

CWLの初期衝動

ところが、論文に載っている手順に従っても、それが「再現」できないという問題が起きます。

f:id:otiai10:20171225133953p:plain

この点を解決しようと立ち上がったのが、CWLです(雑)。Repeat/Reproduce/Replicate/Reuseについては先述のinutanoさんのエントリが詳しいです。

CWLの実行エンジン

入力としてワークフロー(.cwl)とパラメータ(.jsonなど)を受取り、記載されたワークフローを実行するソフトウェアが必要です。これを公式では「implementation」、日本のコミュニティでは「エンジン」と呼んだりしています。

私もいちおうソフトウェアデベロッパーの端くれとして、manabuishii先輩と一緒に、Go言語でCWLのデコーダとimplementationを実装中です。

CWLの競合

同様の問題意識を持ち始まったプロジェクトは他にも複数あります。

// TODO: 競合のリストがここにくる
- WDLとか?
- nextflowとか?

CWLのここがよい

第1に、バイオインフォが抱える問題(とは言っても僕自身この分野の新参者なのであまり歴史的な背景とか精通してるわけじゃないんですけど)にスポットライトを当て、それを解決しようと立ち上がったのは、素晴らしいことかと思われます。

第2に、国際学会やカンファレンスで精力的にディスカッションやハッカソンをしたり、GitHubで仕様を管理したり、コミュニティ形成に力を入れており、提言やPull-Reqにたいしてもかなり柔軟に取り入れたりしているのは普及したポイントかなと思います。実際、我々が作成中のGo言語実装:yacleも実装例のひとつとして早くもリストされています。

第3に、実際多くの場面で使われていること。国際的に、ワークフローの共有といったらCWLファイルつくっとくかぁ〜みたいな雰囲気がすでに漂っており、また公開されたCWLで記述されたワークフローをクラウド上で動かすウェブサービスなども出てきております。

以上が、CWLもなかなかいいじゃん、というProsになります。

.....

....

...

..

.













f:id:otiai10:20091121210556j:plain

CWLのここが嫌い

そういう上っ面じゃねえんだ、アドベントカレンダーはよぉ!こっからが本題だ。

Webプログラマから見たCWLの罪過、罪過?むずかしい言葉使ってんじゃねえよ!嫌いなところについて!うらみつらみを列挙して行こうと思います。

  1. まず名前が嫌い
    • CommonWorkflowLanguageって言うからにはDSLかな?って思うじゃないですか?違うんですよこれ単なるYAML or JSONなんですよ
    • あと何「Common」って!すべてをかいけつするソフトウェアはなにもかいけつしませんよ?
  2. syntaxが嫌い
  3. Data Modelが嫌い
    • みてくださいこの型array<CommandInputParameter> | map<CommandInputParameter.id, CommandInputParameter.type> | map<CommandInputParameter.id, CommandInputParameter> って!
      • つまり[]Model|map[string]string|map[string]Modelってことです
        • これCとかGoでどうやってstructにデコードしますかね
  4. 表現方法が嫌い
    • 百歩譲って、ある言語でデコードがたいへんなのはその言語のせいだとしましょう。しかしながら、ある1つのことを表現するのに、多くの表現方法があることは、規格として正しいあり方なんでしょうか。
  5. 表現方法が嫌い・その2
    • なんやねん!その2て!!!!!
    • Exrepssionsというclauseがあるんですけど、これがマジやっかい
    • これねーJavaScriptなんですよ!!(しかもES5!)
      • ワークフロー定義の内部がJS要求するっていうのならまだ分かるんですが、ワークフローの記述の表現にある特定のランタイムを要求するのって筋悪すぎじゃないですか
      • しかも、あたりまえだけどめっちゃくちゃいろんなこと出来るので、ワークフロー記述規格としての治安が乱れまくる
  6. 哲学が嫌い
    • 上にも書いたように、かなりいろいろディスカッションして、だいたいのものは取り入れちゃってるので、仕様がどんどん肥大化していって、エンジンの実装者が「どの機能をサポートしないべきか」とかまず考えなきゃいけないレベルになっている
    • "All is nothing" を地で行ってて最高にクール
  7. そして、すでにけっこう使われているのが何よりも嫌
    • プラットフォームつくりました!という話しても「それ、CWL食える?」みたいな質問が普通に飛んでくるぐらいには普及してしまっている
    • 過渡期に、パイ取った感じある
    • もう避けては通れない






f:id:otiai10:20091009172516j:plain

提言と提案

  • ワークフローを記述する規格は
    • DSLではなく、一般的なデータ構造フォーマットであるべきだ
    • 静的型付けの厳しい言語であっても、多くの一般的な言語でデコードが容易であるべきだ
      • 多少冗長であっても、厳格な構造をしているべきで、型ゆるゆるでは嫌だ
    • それ自身の解釈に特定のランタイムを要求するべきではない
    • 表現を絞り、 具体的に解決したい問題 をメッセージとして発信し続ける
  • 我々はバイオインフォマティシャンは*1
    • CWL+cwltoolでもいいし、何かほかの方法でもいいが、自分が直面している問題を解決することにフォーカスしたワークフローの記述&実行を心がけるべき
    • そういった「ぼくのかんがえたさいきょうのわーくふろー大会」をやりましょう!
      • painを同じくする仲間が見つかれば、たのしいですね

f:id:otiai10:20171225182717j:plain

雑感

  • ちょっと遅くなりましたが、とびきりエモいやつを書きました
  • 大口を叩きましたが、今後ともご指導ご鞭撻のほど、よろしくお願い致します

DRY

*1:や、俺はただのデベロッパであってバイオインフォマティシャンではないが

GKEを使ったバッチジョブ実行

これは Google Cloud Platform Advent Calendar 2017 - Qiita の19日目のエントリです。otiai10です。

背景

  • 大規模なデータの取扱いと演算を非同期に行いたい
  • 上記を、任意のタイミングで発火させたい
  • 上記は、特定のランタイムではなく、任意のランタイムを要求されうる
    • 計算リソースも、任意の量が要求されうる

ということで、k8sでやってみるかということになりました。GKEはその昔1度だけ触ったことがある*1けど、だいぶ記憶が無い。

k8s(Kubernetes)とは

クバネテス、って僕はよんでます。コンテナを実行する物理ホストないしそのクラスタを抽象化し、コンテナの死活監視やスケールを管理するコンテナオーケストレーションミドルウェア、だと理解しました。

docker-swarmと何がちがうの?

って思ったので、ちょっとググった。「似ているものとどう違うか」を知ることは、それを知るのに非常に有用です。

自分なりにまとめると

  • 解決する問題とそのレイヤーはほぼ同じ
  • Kubernetesはクラウド上のマネージドサービスとして使うのがよさそう。自分で立てるなら、Docker Swarmのほうが簡単だよ。
  • 概念はKubernetesのほうが多いから、Dockerから来た場合は、ちょいちょい学習コストはありそうだよ。
  • でも、やっぱりいろいろ複雑なことはKubernetesのほうができそう
    • とくにvolumesまわりは、とっても気になるよ

GKEとは

で、いよいよGKE

要件の整理

  1. Job on Kubernetesの確認
    1. GKE上にちいさいクラスタを立てる
    2. kubectlでJobをサブミットする
    3. 実行されたことを確認できれば勝ち
  2. GKEのCluster Scalingの評価
    1. GKE上に小さいクラスタを立てる
    2. 重めのJobを複数サブミットする
    3. nodeが増えることを確認
    4. Jobが終わったらnodeが最初のサイズに戻れば勝ち

実装例

0. kubectlのインストール

% gcloud components install kubectl
% which kubectl
/Users/otiai10/.google-cloud-sdk/bin/kubectl
% kubectl --help

1-a: クラスタ立てる

f:id:otiai10:20171219115141p:plain

f:id:otiai10:20171219115344p:plain

f:id:otiai10:20171219115453p:plain

f:id:otiai10:20171219115953p:plain

f:id:otiai10:20171219121616p:plain

ふむ

f:id:otiai10:20171219122250p:plain

1-b. ジョブの実行

% mkdir /tmp/gke-workspace
% cd /tmp/gke-workspace
apiVersion: batch/v1
kind: Job
metadata:
    name: my-first-batch-job
spec:
    template:
        metadata:
            name: my-first-batch-job-tpl
        spec:
            containers:
                -
                    name: my-fbj-container-01
                    image: perl
                    command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
            restartPolicy: Never
    backoffLimit: 4
% kubectl create -f ./my-job.yaml
W1219 14:24:30.058767   46656 factory_object_mapping.go:423] Failed to download OpenAPI (Get http://localhost:8080/swagger-2.0.0.pb-v1: dial tcp [::1]:8080: getsockopt: connection refused), falling back to swagger
The connection to the server localhost:8080 was refused - did you specify the right host or port?

んなぁ〜、まあそうだよな、現状、ローカルのkubectlは、GCPコンソールでつくったクラスタの存在なんて知らないわけだから。

コレに従って、gcloud経由でクラスタ作ってたら、こうはならなかったと思われる。まあしかたないので、kubectlに、GCPコンソールでつくったクラスタに向いてもらう。

% kubectl --help
% kubectl cluster-info
% kubectl config get-clusters
% kubectl config view
% gcloud projects list
% gcloud config set project gke-batch-test
% gcloud container clusters list
NAME             ZONE               MASTER_VERSION  MASTER_IP     MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
my-tiny-cluster  asia-northeast1-a  1.7.8-gke.0     35.200.66.24  n1-standard-1  1.7.8-gke.0   1          RUNNING
% gcloud container clusters get-credentials my-tiny-cluster
Fetching cluster endpoint and auth data.
kubeconfig entry generated for my-tiny-cluster.

そうすると、

% kubectl cluster-info
Kubernetes master is running at https://35.200.66.24
GLBCDefaultBackend is running at https://35.200.66.24/api/v1/namespaces/kube-system/services/default-http-backend/proxy
Heapster is running at https://35.200.66.24/api/v1/namespaces/kube-system/services/heapster/proxy
KubeDNS is running at https://35.200.66.24/api/v1/namespaces/kube-system/services/kube-dns/proxy
kubernetes-dashboard is running at https://35.200.66.24/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
%

となる。いい感じに、kubectlが、今まで知りえなかったGCP上のkubernetesクラスタの情報を持っている状態になった。ので、

1-c. ジョブ実行と成果確認

% kubectl create -f ./my-job.yaml
error: error validating "./my-job.yaml": error validating data: [ValidationError(Job): ValidationError(Job.spec): unknown field "backoffLimit" in io.k8s.kubernetes.pkg.apis.batch.v1.JobSpec]; if you choose to ignore these errors, turn validation off with --validate=false

んなぁ〜、backoffLimitって公式のquickstartにあるやつだぞ?しかたがないのでmy-job.yamlにおけるbackoffLimitコメントアウトした。

% kubectl create -f ./my-job.yaml
job "my-first-batch-job" created

% kubectl describe jobs/my-first-batch-job
Name:           my-first-batch-job
Namespace:      default
Selector:       controller-uid=19e50d15-e484-11e7-acda-42010a920fe9
Labels:         controller-uid=19e50d15-e484-11e7-acda-42010a920fe9
                job-name=my-first-batch-job
Annotations:    <none>
Parallelism:    1
Completions:    1
Start Time:     Tue, 19 Dec 2017 15:16:07 +0900
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=19e50d15-e484-11e7-acda-42010a920fe9
           job-name=my-first-batch-job
  Containers:
   my-fbj-container-01:
    Image:  perl
    Port:   <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  1m    job-controller  Created pod: my-first-batch-job-qmmrn
%

いい感じ。さらに、GCEのCPU使用率のグラフが

f:id:otiai10:20171219152001p:plain

いい感じ。

2-a. クラスタ立てる

使いまわすので割愛。

2-b. 重めのジョブを複数投げる

円周率2000桁で、CPU使用率が30%ぐらいだったので、まー適当に2万桁ぐらい行ってみますか!

 apiVersion: batch/v1
 kind: Job
 metadata:
-    name: my-first-batch-job
+    name: heavy-job
 spec:
     template:
         metadata:
-            name: my-first-batch-job-tpl
+            name: heavy-job-tpl
         spec:
             containers:
                 -
-                    name: my-fbj-container-01
+                    name: heavy-job-container
                     image: perl
-                    command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
+                    command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(20000)"]
             restartPolicy: Never
 #    backoffLimit: 4

2-c.d.

metadata.name をユニークにする必要があったので、ここを変えつつ、

% kubectl create -f ./my-job.yaml
# ちょっと時間たって
% kubectl create -f ./my-job.yaml
# ちょっと時間たって
% kubectl create -f ./my-job.yaml

とした。

f:id:otiai10:20171219154558p:plain

サイズが増えとる。

ちょっと手間取って、スケールダウンのほうは確認できなかったけど、まあ重いジョブを投げて自動的にスケールアップするのは確認できた。

今後の検証事項

  • 投げるジョブがおもすぎてkubectl job delete {NAME}してしまって、自動スケールダウンが確認できなかった
  • 明示的に kubectl job delete {NAME} すると、system podの管理を外れて、オートスケールダウンが発火しなくなるのか?
  • GCPコンソールでGCEを消しても、Kubernetesクラスタクラスタサイズの表記が2のままだったのも、同じ性質の問題か?
  • 「計算リソースも、任意の量が要求されうる」という要件があったのだけれど、どうやらこれに関しては、ある程度見積もれるジョブが来ることが期待されているっぽいので、あるクラスタにおいて1つのジョブが実行されるpodのリソースは、クラスタ定義における「マシンタイプ」を超えないようだ。これに関しては、別のソリューションを考える必要があるっぽい

あとかたづけ

f:id:otiai10:20171219161749p:plain

雑感

  • GCPアドベントカレンダー、GKEの話が多いきがする
  • 先日、美容師さんに勧められた毛生え薬使い始めたんですが、進捗なかなかいいです

現場からは以上です。皆さん良いお年を!

DRYな備忘録として

Azure CLIを使ってインスタンスの作成、sshまで

前回 Azureことはじめ - DRYな備忘録 をやったので、今回はCLIをやります。

とりあえずbrewでやるか

% brew update
% brew search azure-cli
% brew install azure-cli
% which az
/usr/local/bin/az
% az --version
pyenv: python3.6: command not found

The `python3.6' command exists in these Python versions:
  3.6.1

うーん、Python。とりあえずローカルで動くようにしてみるか。

% pyenv install 3.6.0
% cd /tmp
% pyenv local 3.6.0
% az --version
azure-cli (2.0.22)

# 以下略

とりあえずローカルでazコマンドがうごくようになった。

というのがだるいので

docker使お?ね?

% docker run -it --rm azuresdk/azure-cli-python
Unable to find image 'azuresdk/azure-cli-python:latest' locally
latest: Pulling from azuresdk/azure-cli-python
# 中略
Status: Downloaded newer image for azuresdk/azure-cli-python:latest
bash-4.3#
bash-4.3# az --version
azure-cli (2.0.23)

# 以下略
bash-4.3# exit
% docker 

これで十分である。

ということで、brew uninstall azure-cliしてめでたしめでたし。

ログインをする必要がある

% docker run -it azuresdk/azure-cli-python
bash-4.3#
bash-4.3# az login
To sign in, use a web browser to open the page https://aka.ms/devicelogin and enter the code XXXXXXX to authenticate.

ほう。gcloud init とかでは、callback付きのURLを開かせて、ブラウザで表示されたワンタイムトークンをCLIに食わせるタイプだったけど、az loginだと、CLIが表示するワンタイムトークンをブラウザに食わせて認証を完了させるタイプなのか。

で、ブラウザで https://aka.ms/devicelogin に行き、 XXXXXXX を食わせると、認証が完了し、

bash-4.3# az account list

で、確認できる。

Computeインスタンスをつくるまでのみちのり

まず、location idのリストがインターネッツに一切無いという...

bash-4.3# az account list-locations | grep -B1 -A6 Japan
  {
    "displayName": "Japan West",
    "id": "/subscriptions/caa7f0e9-5fbf-4ba0-971d-4e038b0deaff/locations/japanwest",
    "latitude": "34.6939",
    "longitude": "135.5022",
    "name": "japanwest",
    "subscriptionId": null
  },
  {
    "displayName": "Japan East",
    "id": "/subscriptions/caa7f0e9-5fbf-4ba0-971d-4e038b0deaff/locations/japaneast",
    "latitude": "35.68",
    "longitude": "139.77",
    "name": "japaneast",
    "subscriptionId": null
  },

japaneast やね。

Resource Groupをつくる

bash-4.3# az group create -n myRG -l japaneast
bash-4.3# az group list

できてることが確認できる。

Computeインスタンスつくる。

まずssh keyつくって

bash-4.3# mkdir -p ~/.ssh/azure
bash-4.3# ssh-keygen -f ~/.ssh/azure/id_rsa
bash-4.3# chmod 600 ~/.ssh/azure/id_rsa.pub

いざ

bash-4.3# az vm create -n TestTestHoge \
  -g myRG \
  --size Standard_A0 \
  --image debian \
  --admin-username otiai10 \
  --ssh-key-value ~/.ssh/azure/id_rsa.pub

レスポンス↓

{
  "fqdns": "",
  "id": "/subscriptions/xxxxxx/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/TestTestHoge",
  "location": "japaneast",
  "macAddress": "00-0D-3A-50-66-07",
  "powerState": "VM running",
  "privateIpAddress": "10.0.0.4",
  "publicIpAddress": "52.243.44.159",
  "resourceGroup": "myRG",
  "zones": ""
}

確認

bash-4.3# az vm list

sshしてみる

bash-4.3# ssh -i ~/.ssh/azure/id_rsa otiai10@52.243.44.159
otiai10@TestTestHoge:~$
otiai10@TestTestHoge:~$ uname -a
Linux TestTestHoge 3.16.0-4-amd64 #1 SMP Debian 3.16.43-2+deb8u5 (2017-09-19) x86_64 GNU/Linux
otiai10@TestTestHoge:~$
otiai10@TestTestHoge:~$ exit
bash-4.3# uname -a
Linux 58218864392b 4.9.49-moby #1 SMP Fri Dec 8 13:40:02 UTC 2017 x86_64 Linux
bash-4.3#

いい感じ。

あとかたづけ

まずリソース一覧を取得したい

bash-4.3# az resource list -g myRG -l japaneast

つぎに、消せるものから消していく。

まずはVM

bash-4.3# az resource list -g myRG -l japaneast --resource-type=Microsoft.compute/virtualmachines
[
  {}
]
bash-4.3# az vm delete -g myRG -n TestTestHoge
bash-4.3# az resource list -g myRG -l japaneast --resource-type=Microsoft.compute/virtualmachines
[]

done。

Public IP Address消す。

bash-4.3# az resource list -g myRG -l japaneast --resource-type=Microsoft.Network/publicIPAddresses
bash-4.3# az network public-ip list -g myRG
[
  {}
]
bash-4.3# az network public-ip delete -n TestTestHogePublicIP -g myRG
# ネットワークインターフェースを先に削除しろというエラー
bash-4.3# az network nic list -g myRG
[
  {}
]
bash-4.3# az network nic delete -n TestTestHogeVMNic -g myRG
bash-4.3# az network nic list -g myRG
[]
bash-4.3# az network public-ip delete -n TestTestHogePublicIP -g myRG
bash-4.3# az network public-ip list -g myRG
[]

done。

Disk消すか。

bash-4.3# az disk list -g myRG
[
  {}
]
bash-4.3# az disk delete -n TestTestHoge_OsDisk_1_86297f169b6045618fb6c40fe2172657 -g myRG
bash-4.3# az disk list -g myRG
[]

done。

SecurityGroup消す。

bash-4.3# az resource list -g myRG --resource-type=Microsoft.Network/networkSecurityGroups
[
  {}
]
bash-4.3# az resource delete -n TestTestHogeNSG -g myRG --resource-type=Microsoft.Network/networkSecurityGroups
bash-4.3# az resource list -g myRG --resource-type=Microsoft.Network/networkSecurityGroups
[]

ラスト、VN消す。

bash-4.3# az resource list -g myRG
[
  {}
]
bash-4.3# az resource delete -n TestTestHogeVNET -g myRG --resource-type=Microsoft.Network/virtualNetworks
bash-4.3# az resource list -g myRG
[]

これで綺麗になった。

f:id:otiai10:20171217185431p:plain

雑感

  • azure cli、べんり。というか親切。--helpが充実している。
  • 5000兆円とか言わないので、200万円ぐらいほしい。

DRYな備忘録として