DockerfileからビルドしたDockerイメージをDocker Hubにアップする

個人的によく使うDockerイメージをpublicに置いておきたかったので初めてやってみた。

今回実際に上げたのはこちら。 https://hub.docker.com/r/chokkoy/redis_trib/

Docker Hubのアカウントを取る

https://hub.docker.com/ から、画面に沿って行う。

Dockerfileを書いて、イメージをビルド

下記のような状態で、

~/Documents/docker/image ᐅ ls
Dockerfile
~/Documents/docker/image ᐅ cat Dockerfile
FROM ubuntu:16.04

RUN apt-get update
RUN apt-get install -y ruby vim wget redis-tools
RUN gem install redis
RUN wget http://download.redis.io/redis-stable/src/redis-trib.rb

下記のコマンドでビルドする

docker build -t chokkoy/redis_trib:1.0 ./

できている

~/Documents/docker/image ᐅ docker images | grep redis_trib                                                                                    
chokkoy/redis_trib                                            1.0                 8a146f5d717b        About an hour ago   245MB
  • docker loginでログインする

  • docker push chokkoy/redis_tribでイメージをPush

これでOK

Docker for Macに同梱のKubernetesを使う(minikubeの代替?)

Docker for MacKubernetesが同梱されるようになったことはなんとなく知っていたが、どのように使うのかは分かっていなかったので試してみた。

現時点でのDocker for MacのバージョンはVersion 18.06.0-ce-mac70 (26399)

f:id:road288:20180810161026p:plain

Preferenceをみると、Kubernetesというタブができているので、それを開いてEnable Kubernetesにチェックを入れる。

f:id:road288:20180810161116p:plain

Defaultのオーケストレーションツールを選択する。SwarmはつかったことないのでKubernetesにする(デフォルトはSwarmになっていた)。

f:id:road288:20180810161212p:plain

画面に沿ってインストールする。インストールは数分かかった。 f:id:road288:20180810161220p:plain

インストールが終わると、docker for desktopというコンテキストが用意されていて、ターミナル上もそれに切り替わっている。

ᐅ kubectl config current-context                                                                                              
docker-for-desktop

defaultのネームスペースにはpodがないが、他のネームスペースにはpodがいくつか起動済みになっている。

~ ᐅ kubectl get pods

No resources found.
~ ᐅ kubectl get namespace

NAME          STATUS    AGE
default       Active    3m
docker        Active    3m
kube-public   Active    3m
kube-system   Active    3m
~ ᐅ kubectl get pods --namespace docker

NAME                           READY     STATUS    RESTARTS   AGE
compose-7447646cf5-bk579       1/1       Running   0          3m
compose-api-6fbc44c575-jg2dp   1/1       Running   0          3m
~ ᐅ kubectl get pods --namespace kube-public

No resources found.
~ ᐅ kubectl get pods --namespace kube-system

NAME                                         READY     STATUS    RESTARTS   AGE
etcd-docker-for-desktop                      1/1       Running   0          2m
kube-apiserver-docker-for-desktop            1/1       Running   0          3m
kube-controller-manager-docker-for-desktop   1/1       Running   0          2m
kube-dns-86f4d74b45-kddhh                    3/3       Running   0          3m
kube-proxy-nnsx8                             1/1       Running   0          3m
kube-scheduler-docker-for-desktop            1/1       Running   0          2m

nodeが1つなのは当然として、kubernetesというserviceがすでに存在している。このへんはkubernetesのデフォのものなのか、docker for macならではの設定なのかは調べてない。

~ ᐅ kubectl get nodes             
NAME                 STATUS    ROLES     AGE       VERSION
docker-for-desktop   Ready     master    16m       v1.10.3
~ ᐅ kubectl get services         
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   16m
~ ᐅ kubectl describe service kubernetes 
Name:              kubernetes
Namespace:         default
Labels:            component=apiserver
                   provider=kubernetes
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP:                10.96.0.1
Port:              https  443/TCP
TargetPort:        6443/TCP
Endpoints:         192.168.65.3:6443
Session Affinity:  ClientIP
Events:            <none>

というか、get all --all-namespacesという便利なコマンドがあった。

~ ᐅ kubectl get all --all-namespaces   
NAMESPACE     NAME            DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
kube-system   ds/kube-proxy   1         1         1         1            1           <none>          4m

NAMESPACE     NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
docker        deploy/compose       1         1         1            1           4m
docker        deploy/compose-api   1         1         1            1           4m
kube-system   deploy/kube-dns      1         1         1            1           4m

NAMESPACE     NAME                        DESIRED   CURRENT   READY     AGE
docker        rs/compose-7447646cf5       1         1         1         4m
docker        rs/compose-api-6fbc44c575   1         1         1         4m
kube-system   rs/kube-dns-86f4d74b45      1         1         1         4m

NAMESPACE     NAME                                            READY     STATUS    RESTARTS   AGE
docker        po/compose-7447646cf5-fn2ft                     1/1       Running   0          4m
docker        po/compose-api-6fbc44c575-bcg82                 1/1       Running   0          4m
kube-system   po/etcd-docker-for-desktop                      1/1       Running   0          3m
kube-system   po/kube-apiserver-docker-for-desktop            1/1       Running   0          3m
kube-system   po/kube-controller-manager-docker-for-desktop   1/1       Running   0          3m
kube-system   po/kube-dns-86f4d74b45-ggqq4                    3/3       Running   0          4m
kube-system   po/kube-proxy-z9886                             1/1       Running   0          4m
kube-system   po/kube-scheduler-docker-for-desktop            1/1       Running   0          3m

NAMESPACE     NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE
default       svc/kubernetes    ClusterIP   10.96.0.1      <none>        443/TCP         4m
docker        svc/compose-api   ClusterIP   10.102.8.163   <none>        443/TCP         4m
kube-system   svc/kube-dns      ClusterIP   10.96.0.10     <none>        53/UDP,53/TCP   4m

このdocker for desktopをリセットしたいとき

docker for macのPreferenceにResetという項目があり、そこでKubernetesのリセットもできるようになっている。

docker composeの設定ファイルを使う

docker stackコマンドでdocker-compose.ymlもKubernetesへデプロイできると書いてあって試してみたが、docker composeのversion1や2には対応していない。version 3のサンプルを使ったら動作した。

ᐅ docker stack deploy --compose-file docker-compose.yml words                                                                        
unsupported Compose file version: 1.0

参考

[和訳] デスクトップ向けDockerでのDocker ComposeとKubernetes #docker #kubernetes #k8s - クリエーションライン株式会社

Compose file version 3 reference | Docker Documentation

GCPのロードバランサの仕組みの復習

あいまいな理解でなんとなく使っているので復習がてらメモ。

下記を参考にまとめる。

www.ianlewis.org

リンク先から引用するが、2枚目のダイアグラムの画像がすごくわかりやすい。

f:id:road288:20180717190925p:plain

  • まずはヘルスチェックを作る

    • ヘルスチェックは他のリソースに依存しない
    • バックエンドサービスにアタッチすると動作を始める。それまでは作っただけでは何もしない
    • Compute Engineリソースのカテゴリに存在する
  • バックエンドサービス

    • 実体はインスタンスグループへのリンクだが、ポート番号とロードバランシングモードを設定できる。そこで設定したポート番号はnamed portとして設定する
  • バックエンド

    • バックエンドサービスとは別。バックエンドサービスはいくつかのバックエンドを束ねる。バックエンドサービスにヘルスチェックがひもづき、バックエンドにインスタンスグループがひもづく。(上掲のダイアグラムを参照)
  • URLマップ(Url Maps)

    • ホスト(host rules)とURLのマッピング(path matchers)で構成。
    • デフォルトでつなぐバックエンドサービスを設定する。他にマッチするものが無ければこれが利用される。1つしか作らないのであればこれだけで十分。
  • ターゲットプロキシ

    • フォワーディングルールとURLマップの間に挟まるもの
    • ここでユーザーの接続はterminateする
    • 主にSSL証明書を設定する
  • フォワーディングルール

    • IPアドレスをロードバランサ・ターゲットプロキシとひもづける
    • 実際に課金されるのはここ
    • IPアドレスは事前に別で作っておく

Goならわかるシステムプログラミング:第3章

  • 標準入力
package main

import (
    "os"
    "io"
    "fmt"
)

func main() {
    for {
        buffer := make([]byte, 5)
        size, err := os.Stdin.Read(buffer)
        if err == io.EOF {
            fmt.Println("EOF")
            break
        }
        fmt.Printf("size=%d input='%s'\n", size, string(buffer))
    }
}
  • ファイル入力
package main

import (
    "os"
    "io"
)

func main() {
    file, err := os.Open("main.go")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    io.Copy(os.Stdout, file)
}
  • ネットワーク通信の読み込み
package main

import (
    "net"
    "net/http"
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    conn, err := net.Dial("tcp", "ascii.jp:80")
    if err != nil {
        panic(err)
    }
    conn.Write([]byte("GET / HTTP/1.0\r\nHost: ascii.jp\r\n\r\n"))
    res, err := http.ReadResponse(bufio.NewReader(conn), nil)
    fmt.Println(res.Header)
    defer res.Body.Close()
    io.Copy(os.Stdout, res.Body)

}
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
)

func main() {
    data := []byte{0x0, 0x0, 0x27, 0x10}
    var i int32
    binary.Read(bytes.NewReader(data), binary.BigEndian, &i)
    fmt.Printf("data: %d\n", i)
}
  • PNGファイルを分析してみる
package main

import (
    "io"
    "encoding/binary"
    "fmt"
    "os"
)

func dumpChunk(chunk io.Reader) {
    var length int32
    binary.Read(chunk, binary.BigEndian, &length)
    buffer := make([]byte, 4)
    chunk.Read(buffer)
    fmt.Printf("chunk, '%v' (%d bytes)\n", string(buffer), length)
}

func readChunks(file *os.File) []io.Reader {
    var chunks []io.Reader
    file.Seek(8, 0)
    var offset int64 = 8

    for {
        var length int32
        err := binary.Read(file, binary.BigEndian, &length)
        if err == io.EOF {
            break
        }
        chunks = append(chunks, io.NewSectionReader(file, offset, int64(length)+12))
        offset, _ = file.Seek(int64(length+8), 1)
    }
    return chunks
}

func main() {
    file, err := os.Open("Lenna.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    chunks := readChunks(file)

    for _, chunk := range chunks {
        dumpChunk(chunk)
    }
}

実行結果

chunk, 'IHDR' (13 bytes)
chunk, 'sRGB' (1 bytes)
chunk, 'IDAT' (473761 bytes)
chunk, 'IEND' (0 bytes)

Process finished with exit code 0
  • PNG画像に秘密のテキストを入れてみる
package main

import (
    "io"
    "bytes"
    "encoding/binary"
    "hash/crc32"
    "os"
    "fmt"
)

func textChunk(text string) io.Reader {
    byteData := []byte(text)
    var buffer bytes.Buffer
    binary.Write(&buffer, binary.BigEndian, int32(len(byteData)))
    buffer.WriteString("tExt")
    buffer.Write(byteData)
    crc := crc32.NewIEEE()
    io.WriteString(crc, "tExt")
    binary.Write(&buffer, binary.BigEndian, crc.Sum32())
    return &buffer
}
func readChunks(file *os.File) []io.Reader {
    var chunks []io.Reader
    file.Seek(8, 0)
    var offset int64 = 8

    for {
        var length int32
        err := binary.Read(file, binary.BigEndian, &length)
        if err != nil {
            break
        }
        chunks = append(chunks, io.NewSectionReader(file, offset, int64(length)+12))
        offset , _ = file.Seek(int64(length+8), 1)
    }
    return chunks
}

func dumpChunk(chunk io.Reader) {
    var length int32
    binary.Read(chunk, binary.BigEndian, &length)
    buffer := make([]byte, 4)
    chunk.Read(buffer)
    fmt.Printf("chunk '%v' (%d bytes\n", string(buffer), length)
    if bytes.Equal(buffer, []byte("tExt")) {
        rawText := make([]byte, length)
        chunk.Read(rawText)
        fmt.Println(string(rawText))
    }
}

func writeFile() {
    file, err := os.Open("Lenna.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    newFile, err := os.Create("Lenna2.png")
    if err != nil {
        panic(err)
    }
    chunks := readChunks(file)
    io.WriteString(newFile, "\x89PNG\r\n\x1a\n")
    io.Copy(newFile, chunks[0])
    io.Copy(newFile, textChunk("ASCII PROGRAMMING++"))
    for _, chunk := range chunks[1:] {
        io.Copy(newFile, chunk)
    }
}

func readFile() {
    file, err := os.Open("Lenna2.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    chunks := readChunks(file)
    for _, chunk := range chunks {
        dumpChunk(chunk)
    }
}

func main() {
    writeFile()
    readFile()
}

  • テキスト解析 改行/単語で区切る
package main

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

var source = `1行目
2行目
3行目`

func main() {
    reader :=bufio.NewReader(strings.NewReader(source))
    for {
        line, err := reader.ReadString('\n')
        fmt.Printf("%#v\n", line)
        if err == io.EOF {
            break
        }
    }
}
  • bufio.Scannerを使う
package main

import (
    "bufio"
    "strings"
    "fmt"
)

var source = `1行目
2行目
3行目`

func main() {
    scanner := bufio.NewScanner(strings.NewReader(source))
    for scanner.Scan() {
        fmt.Printf("%#v\n", scanner.Text())
    }
}
  • データ型を指定して解析
package main

import (
    "strings"
    "fmt"
)

var source = "123 1.234 1.0e4 test"

func main() {
    reader := strings.NewReader(source)
    var i int
    var f, g float64
    var s string
    fmt.Fscan(reader, &i, &f, &g, &s)
    fmt.Printf("i=%#v f=%#v g=%#v s=%#v\n", i, f, g, s)
}

実行結果

i=123 f=1.234 g=10000 s="test"

Fscanはスペース(または改行)で区切られたstringを読み込んで2つ目以降の引数の変数に代入していく。 

  • csvフォーマットの文字列の解析
package main

import (
    "strings"
    "encoding/csv"
    "io"
    "fmt"
)

var csvSource =
    `13101, "100 ", "1000003", "トウキョウト", "チヨダク", "ヒトツバシ(1チョウメ)", "東京都", "千代田区", "一ツ橋(1丁目)", 1,0,1,0,0,0 
13101, "101  ", "1000003", "トウキョウト", "チヨダク", "ヒトツバシ(2チョウメ)", "東京都", "千代田区", "一ツ橋(2丁目)", 1,0,1,0,0,0
13101, "100  ", "1000012", "トウキョウト", "チヨダク", "ヒビヤコウエン", "東京都", "千代田区", "日比谷公園", 0,0,0,0,0,0
13101, "102  ", "1000093", "トウキョウト", "チヨダク", "ヒトツバシ(1チョウメ)", "東京都", "千代田区", "平河町", 0,0,1,0,0,0
13101, "102  ", "1000071", "トウキョウト", "チヨダク", "ヒトツバシ(1チョウメ)", "東京都", "千代田区", "富士見", 0,0,1,0,0,0
`

func main() {
    reader := strings.NewReader(csvSource)
    csvReader := csv.NewReader(reader)
    csvReader.LazyQuotes = true
    for {
        line, err := csvReader.Read()
        if err == io.EOF {
            break
        }
        fmt.Println(line[2], line[6:9])
    }
}

実行結果

 "1000003" [ "東京都"  "千代田区"  "一ツ橋(1丁目)"]
 "1000003" [ "東京都"  "千代田区"  "一ツ橋(2丁目)"]
 "1000012" [ "東京都"  "千代田区"  "日比谷公園"]
 "1000093" [ "東京都"  "千代田区"  "平河町"]
 "1000071" [ "東京都"  "千代田区"  "富士見"]

BOM付UTF-8にはまった。書籍そのままでは動かない。

GoでBOM付きのUTF8なCSVを扱うには | tail -f pinzo.log

encoding/csv: bare " in non-quoted-field · Issue #21672 · golang/go · GitHub

  • io.Reader/io.Writerでストリームを自由に操る
package main

import (
    "bytes"
    "io"
    "os"
)

func main() {
    header := bytes.NewBufferString(("----- HEADER -----\n"))
    content := bytes.NewBufferString("Example of io.MultiReader\n")
    footer := bytes.NewBufferString("----- FOOTER -----\n")

    reader := io.MultiReader(header, content, footer)
    io.Copy(os.Stdout, reader)
}

package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
)

func main() {
    var buffer bytes.Buffer
    reader := bytes.NewBufferString("Example of io.TeeReader\n")
    teeReader := io.TeeReader(reader, &buffer)
    _, _ = ioutil.ReadAll(teeReader)
    fmt.Println(buffer.String())
}

Prometheusのドキュメントを読む

  • prometheusを起動
./prometheus --config.file=prometheus.yml

http://localhost:9090でステータスページが見られる http://localhost:9090/metricsで自身のmetricsのendpointが見られる(自分自身のmetricsも収集している)

  • 設定の再読込をするには?
    • PrometheusプロセスにSIGHUPを送る
    • /-/reloadにPOSTリクエストを送る

https://prometheus.io/docs/introduction/faq/#can-i-reload-prometheus-s-configuration

  • バッチジョブの監視はどうする?

    • Pushgatewayを使う
  • データ形式 <metric name>{<label name>=<label value>, ...}

    • 例:api_http_requests_total{method="POST", handler="/messages"}
  • prometheus.ymlの構成

    • global
      • prometheus自体の一般的な設定
    • rule_files
      • 追加で作った計算式を別ファイルに定義し、それを読み込む
    • scrape_configs
      • データを取得しにいくEndpoint(サーバー)などを記載
  • インストール方法

  • prometheus.rules.yml

    • recording_rulesとalerting_rulesがある
    • recording_rulesは集計したい値の追加
    • alerting_rulesはalertmanagerに送る条件の追加
    • prometheusはアラートの発火をシンプルに行うだけ。アラートの集約・通知条件・依存関係の定義などの細かい設定はalertmanagerで行う、というように役割分担する。
  • templateはGo言語のtext/templateパッケージをベースにしている