20200806のGoに関する記事は12件です。

【GoでIPv6】GoネットワークプログラムでIPv6対応させるためのhttpサーバ

理由

Goで書かれたネットワークプログラムをIPv6に対応させるためにはどうすればいいのかコード例をメモしておく

参考コード

main.go
package main
import (
    "log"
    "net/http"
)
func main() {
    log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/var/www/html"))))
}
/var/www/html/index.html
<html>
 <head>
  <title>test</title>
 </head>
 <body>
 <h1>This is ipv6 http server test.</h1>
 </body>
</html>

解説

Goのhttp.ListenAndServeの場合、何も意識する必要はありません

  • サーバのIPv6アドレスは
$ ip address show dev eth0 
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq state UP group default qlen 1000
    link/ether 06:0a:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.92/25 brd 192.168.100.127 scope global dynamic eth0
       valid_lft 3558sec preferred_lft 3558sec
    inet 192.168.100.106/25 brd 192.168.100.127 scope global secondary eth0
       valid_lft forever preferred_lft forever
    inet6 2406:da14:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/128 scope global dynamic 
       valid_lft 393sec preferred_lft 93sec
    inet6 fe80::40a:eaff:xxxx:xxxx/64 scope link 
       valid_lft forever preferred_lft forever
  • runする
$ go run main.go  
  • 確認
$ netstat -an | grep 8080
tcp6       0      0 :::8080                 :::*                    LISTEN    
  • アクセスしてみる
$ curl -i 'http://[2406:da14:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:8080/' 
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 112
Content-Type: text/html; charset=utf-8
Last-Modified: Thu, 06 Aug 2020 12:17:04 GMT
Date: Thu, 06 Aug 2020 12:22:11 GMT

<html>
 <head>
  <title>test</title>
 </head>
 <body>
 <h1>This is ipv6 http server test.</h1>
 </body>
</html>

まとめ

サーバにIPv6アドレスつけて、フィルタ/ファイアーウォール/セキュリティグループ等開けてあげれば、なにもしなくていいよ。
参考)AWS VPCでIPv6を使う

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

【GoネットワークプログラムでIPv6対応させる】IPv6対応httpサービス

理由

Goで書かれたネットワークプログラムをIPv6に対応させるためにはどうすればいいのかコード例をメモしておく

参考コード

main.go
package main
import (
    "log"
    "net/http"
)
func main() {
    log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/var/www/html"))))
}
/var/www/html/index.html
<html>
 <head>
  <title>test</title>
 </head>
 <body>
 <h1>This is ipv6 http server test.</h1>
 </body>
</html>

解説

Goのhttp.ListenAndServeの場合、何も意識する必要はありません

  • サーバのIPv6アドレスは
$ ip address show dev eth0 
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq state UP group default qlen 1000
    link/ether 06:0a:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.92/25 brd 192.168.100.127 scope global dynamic eth0
       valid_lft 3558sec preferred_lft 3558sec
    inet 192.168.100.106/25 brd 192.168.100.127 scope global secondary eth0
       valid_lft forever preferred_lft forever
    inet6 2406:da14:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/128 scope global dynamic 
       valid_lft 393sec preferred_lft 93sec
    inet6 fe80::40a:eaff:xxxx:xxxx/64 scope link 
       valid_lft forever preferred_lft forever
  • runする
$ go run main.go  
  • 確認
$ netstat -an | grep 8080
tcp6       0      0 :::8080                 :::*                    LISTEN    
  • アクセスしてみる
$ curl -i 'http://[2406:da14:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:8080/' 
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 112
Content-Type: text/html; charset=utf-8
Last-Modified: Thu, 06 Aug 2020 12:17:04 GMT
Date: Thu, 06 Aug 2020 12:22:11 GMT

<html>
 <head>
  <title>test</title>
 </head>
 <body>
 <h1>This is ipv6 http server test.</h1>
 </body>
</html>

まとめ

サーバにIPv6アドレスつけて、フィルタ/ファイアーウォール/セキュリティグループ等開けてあげれば、なにもしなくていいよ。
参考)AWS VPCでIPv6を使う

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

【GoネットワークプログラムでIPv6対応させる】IPv6対応httpサービスの作り方

理由

Goで書かれたネットワークプログラムをIPv6に対応させるためにはどうすればいいのかコード例をメモしておく

参考コード

main.go
package main
import (
    "log"
    "net/http"
)
func main() {
    log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/var/www/html"))))
}
/var/www/html/index.html
<html>
 <head>
  <title>test</title>
 </head>
 <body>
 <h1>This is ipv6 http server test.</h1>
 </body>
</html>

解説

Goのhttp.ListenAndServeの場合、何も意識する必要はありません

  • サーバのIPv6アドレスは
$ ip address show dev eth0 
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq state UP group default qlen 1000
    link/ether 06:0a:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.92/25 brd 192.168.100.127 scope global dynamic eth0
       valid_lft 3558sec preferred_lft 3558sec
    inet 192.168.100.106/25 brd 192.168.100.127 scope global secondary eth0
       valid_lft forever preferred_lft forever
    inet6 2406:da14:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/128 scope global dynamic 
       valid_lft 393sec preferred_lft 93sec
    inet6 fe80::40a:eaff:xxxx:xxxx/64 scope link 
       valid_lft forever preferred_lft forever
  • runする
$ go run main.go  
  • 確認
$ netstat -an | grep 8080
tcp6       0      0 :::8080                 :::*                    LISTEN    
  • アクセスしてみる
$ curl -i 'http://[2406:da14:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:8080/' 
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 112
Content-Type: text/html; charset=utf-8
Last-Modified: Thu, 06 Aug 2020 12:17:04 GMT
Date: Thu, 06 Aug 2020 12:22:11 GMT

<html>
 <head>
  <title>test</title>
 </head>
 <body>
 <h1>This is ipv6 http server test.</h1>
 </body>
</html>

まとめ

サーバにIPv6アドレスつけて、フィルタ/ファイアーウォール/セキュリティグループ等開けてあげれば、なにもしなくていいよ。
参考)AWS VPCでIPv6を使う

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

gRPCとは

RPCとは

RPCはRemoteProcedureCallの略で、逐語的に訳すと「遠隔手続き呼び出し」となります。
すなわち、「あるプログラムがネットワーク上の異なる場所に配置されたプログラムを呼び出して実行すること」を指します。
あるサービスから別のサービスのアプリケーションの処理(サブルーチン/クラス/関数など)を呼び出すことができ、RPCを使うことで、違うアプリケーションのロジックを、あたかも自分のアプリケーションの中に実装されているかのように扱うことができます。
gRPC以外にも「JSON-RPC」や「SOAP」なども。

gRPC

Googleが2004年に作ったはGoogleの各マイクロサービス間のRPC、「Stubby」を起源とし、http/2などの規格登場に合わせて汎用的に作り直したオープンソースのプロダクト。

REST-APIと比べた時のgRPCの利点

HTTP/2による通信の高パフォーマンス

  • HTTP/2では通信時にデータがテキストではなくバイナリにシリアライズされて送られます。そのため、小さな容量で転送でき、ネットワーク内のリソースをより効率的に使用することができます。また、HTTP/2ではストリームを使い、ひとつのコネクションで複数のリクエスト/レスポンスをやり取りできます。つまり、リクエスト/レスポンスの度にデータを接続、切断を繰り返す必要がなく、その都度ヘッダーを送らなくて済むので、高速で効率的な通信が可能となります。 特にマイクロサービスでは通信の回数が多くなり、応答速度の遅延がサービス全体のボトルネックになりがちなので、これを解決できます。

Protcol Buffersでのスキーマファーストの開発により生産性が高い

  • gRPCではProtocolBuffersのフォーマットにシリアライズ/でシリアライズしてリクエストレスポンスでデータをやり取りします。.protoファイルにスキーマを書いて、コンパイラを実行すると任意の言語のサーバー/クライアント用コードを自動生成してくれます。 この.protoファイルを見ると、APIの仕様が分かるので、API側、クライアント側で担当が違うような状況でも開発ができて便利だし、間違いが起きにくいです。

柔軟なストリーミング方式

  • シンプルなRPC
    • クライアントから送られたひとつのリクエストに対して、レスポンスを一度返すもっとも基本的な形式
  • サーバーストリーミングRPC
    • クライアントから送られたリクエストに対して、レスポンスを複数回に分けて返します。時間のかかる処理について、非同期的にレスポンスを返すことができます。クライアントからのリクエスト後、クライアントは待機状態とし、サーバーの状態変更に応じて、情報を随時プッシュしていくこともできます。通知やタイムラインのリアルタイム更新などに有効。こういった場合仮にストリーミング機能使えない場合、クライアント側から定期的にポーリングで情報を引っ張らねばならず無駄な通信が発生してしまうのに比べ、サーバストリーミングPRCは、サーバから直接情報をプッシュできるのでネットワーク負荷を最小化できます。
  • クライアントストリーミングRPC
    • クライアントからリクエストを分割して送り、サーバーは全てのリクエストを受け取る前に処理を逐次開始できる方式。
  • 双方向ストリーミングRPC
    • クライアントから初めのリクエストが送られた後、サーバー・クライアントどちらも任意のタイミングでリクエスト・レスポンスを送ることができます。チャットやゲームなどに有用です。RESTAPIではストリーミングが行えないので、WebSocketサーバーを別途立てる必要などがありました。gRPCの場合、単一のサーバーで双方向通信のAPIとしても使えます。

REST-APIと比べた時のgRPCの欠点

HTTP/2非対応である危険性

  • 例えばAWSのALB(ApplicationLoadBalancer)はgRPCに対応していません15。フロントでHTTP/2のリクエストを受け付けることはできますが、バックエンドのサーバーへの転送をHTTP/1で行うためです。同様に通信経路上でHTTP/2に非対応なサービスがある場合に、gRPCを使う上で問題が生じる場合があります。

ブラウザの対応状況が不十分

  • gRPCWebなどはあるものの、Envoyなどを使ってProxyサーバーを立てる必要があったり、双方向あるいはクライアントストリーミングRPCに非対応であるなど、まだまだ十分な対応状況とはいえないようです18。フロントエンドのアプリケーションとの通信に使うには少し工夫が必要です。SPAとの通信にはRESTAPIの方が、JSONとの親和性の高さもあり良いかもしれません。

バイナリにシリアライズすると人間が読めない

  • JSONの場合は出力を人間が直接読めますが、gRPCの場合は専用のクライアントをインストールする必要があります。

RESTでも通信速度が十分な場合も

  • 基本的にgRPCは RESTよりもミニマムで高速な通信が可能ですが、RESTもそれなりに早く、ボトルネックが通信以外にある場合はそもそも通信速度の優位性は役に立ちません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GOA とE2Eテスト

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

【GoネットワークプログラムでIPv6対応させる】アドレス正規化をする方法

理由

Goで書かれたネットワークプログラムをIPv6に対応させるためには、サーバサイドはあまり意識しなくてもいいが、IPv6アドレスは表記方法が複数あり、このIPv6アドレス処理を文字列で処理しようとする場合正規化する必要がある。その場合のコード例をメモしておく

例)
- 2001:db8::1 は 2001:0db8:0000:0000:0000:0000:0000:0001 ともかける→文字列比較すると違うと判断されてしまう
参考) RFC5952-IPv6アドレスの推奨表記

参考コード

main.go
package main

import (
    "fmt"
    "net"
)

func v6Format(addr string) string {
    ip := net.ParseIP(addr)
    // Parseできたら
    if ip != nil {
        return ip.String()
    } else {
        return "error"
    }
}

func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // v6Formatの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, v6Format(v))
    }
}

解説

v6Formatがどのような動きをするか詳細を見てみると、文字列をParseIPして、Stringで文字列化しているだけ。
これだけで正規化できます

  • input
2001:db8:0:0:1:0:0:1
2001:0db8:0:0:1:0:0:1
2001:db8::1:0:0:1
2001:db8::0:1:0:0:1
2001:0db8::1:0:0:1
2001:db8:0:0:1::1
2001:db8:0000:0:1::1
2001:DB8:0:0:1::1
2001:db8:aaaa:bbbb:cccc:dddd::1
2001:db8:aaaa:bbbb:cccc:dddd:0:1
2001:db8:0:0:0::1
2001:db8:0:0::1
2001:db8:0::1
2001:db8::1
2001:db8::aaaa:0:0:1
2001:db8:0:0:aaaa::1
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
2001:db8:aaaa:bbbb:cccc:dddd:eeee:AAAA
2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa
  • output
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8:aaaa:bbbb:cccc:dddd:0:1
2001:db8:aaaa:bbbb:cccc:dddd:0:1
2001:db8::1
2001:db8::1
2001:db8::1
2001:db8::1
2001:db8::aaaa:0:0:1
2001:db8::aaaa:0:0:1
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa

アンチパターン

やってしまいがちなアンチパターンは文字列処理してしまうこと。正規表現判断することも同じアンチパターンと考える。

main.go
package main

import (
    "fmt"
    "strings"
)

func v6Format(addr string) string {
    addr = strings.Replace(addr, ":0000", ":", -1)
    addr = strings.Replace(addr, ":::", ":", -1)
    addr = strings.Replace(addr, ":0", ":", -1)
    return strings.ToLower(addr)
}

func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:0db8:0000:0000:0000:0000:0000:0001", "2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // v6Formatの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, v6Format(v))
    }
}

FULL形式に全部してしまえ

package main

import (
    "fmt"
    "net"
)

func v6Format(addr string) string {
    ip := net.ParseIP(addr)
    full := ""
    for i, j := range ip {
        if i > 0 && i%2 == 0 {
            full = full + ":"
        }
        full = full + fmt.Sprintf("%02x", j)
    }
    return full
}

func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:0db8:0000:0000:0000:0000:0000:0001", "2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // v6Formatの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, v6Format(v))
    }
}

まとめ

IPv6アドレスを正規化せずに判断するのはNG。正規化を文字列変換するのもNG

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

【GoでIPv6】GoネットワークプログラムでIPv6対応させるためのアドレス正規化をする方法

理由

Goで書かれたネットワークプログラムをIPv6に対応させるためには、サーバサイドはあまり意識しなくてもいいが、IPv6アドレスは表記方法が複数あり、このIPv6アドレス処理を文字列で処理しようとする場合正規化する必要がある。その場合のコード例をメモしておく

例)
- 2001:db8::1 は 2001:0db8:0000:0000:0000:0000:0000:0001 ともかける→文字列比較すると違うと判断されてしまう
参考) RFC5952-IPv6アドレスの推奨表記

参考コード

main.go
package main

import (
    "fmt"
    "net"
)

func v6Format(addr string) string {
    ip := net.ParseIP(addr)
    // Parseできたら
    if ip != nil {
        return ip.String()
    } else {
        return "error"
    }
}

func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // v6Formatの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, v6Format(v))
    }
}

解説

v6Formatがどのような動きをするか詳細を見てみると、文字列をParseIPして、Stringで文字列化しているだけ。
これだけで正規化できます

  • input
2001:db8:0:0:1:0:0:1
2001:0db8:0:0:1:0:0:1
2001:db8::1:0:0:1
2001:db8::0:1:0:0:1
2001:0db8::1:0:0:1
2001:db8:0:0:1::1
2001:db8:0000:0:1::1
2001:DB8:0:0:1::1
2001:db8:aaaa:bbbb:cccc:dddd::1
2001:db8:aaaa:bbbb:cccc:dddd:0:1
2001:db8:0:0:0::1
2001:db8:0:0::1
2001:db8:0::1
2001:db8::1
2001:db8::aaaa:0:0:1
2001:db8:0:0:aaaa::1
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
2001:db8:aaaa:bbbb:cccc:dddd:eeee:AAAA
2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa
  • output
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8:aaaa:bbbb:cccc:dddd:0:1
2001:db8:aaaa:bbbb:cccc:dddd:0:1
2001:db8::1
2001:db8::1
2001:db8::1
2001:db8::1
2001:db8::aaaa:0:0:1
2001:db8::aaaa:0:0:1
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa

アンチパターン

やってしまいがちなアンチパターンは文字列処理してしまうこと。正規表現判断することも同じアンチパターンと考える。

main.go
package main

import (
    "fmt"
    "strings"
)

func v6Format(addr string) string {
    addr = strings.Replace(addr, ":0000", ":", -1)
    addr = strings.Replace(addr, ":::", ":", -1)
    addr = strings.Replace(addr, ":0", ":", -1)
    return strings.ToLower(addr)
}

func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:0db8:0000:0000:0000:0000:0000:0001", "2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // v6Formatの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, v6Format(v))
    }
}

FULL形式に全部してしまえ

package main

import (
    "fmt"
    "net"
)

func v6Format(addr string) string {
    ip := net.ParseIP(addr)
    full := ""
    for i, j := range ip {
        if i > 0 && i%2 == 0 {
            full = full + ":"
        }
        full = full + fmt.Sprintf("%02x", j)
    }
    return full
}

func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:0db8:0000:0000:0000:0000:0000:0001", "2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // v6Formatの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, v6Format(v))
    }
}

まとめ

IPv6アドレスを正規化せずに判断するのはNG。正規化を文字列変換するのもNG

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

【SRE/Go】database/sql のざっとした情報

Database

全体像は下記から把握する
Go SQL Interface (Japanese) · golang.shop
Go database/sql チュートリアル 04 - 結果セットの取得 · golang.shop

Scan

Scanは、一致した行の列をdestが指す値にコピーします。詳細については、Rows.Scanのドキュメントを参照してください。複数の行がクエリと一致する場合、Scanは最初の行を使用し、残りは破棄します。クエリに一致する行がない場合、ScanはErrNoRowsを返します。
sql - The Go Programming Language

Query

Queryは、指定された引数を使用して準備されたクエリステートメントを実行し、クエリ結果を* Rowsとして返します。
sql - The Go Programming Language

Next

Nextは、Scanメソッドで読み取るために次の結果行を準備します。成功した場合はtrueを返し、次の結果行がない場合や準備中にエラーが発生した場合はfalseを返します。 2つのケースを区別するには、Errを参照する必要があります。
Scanへのすべての呼び出しは、最初の呼び出しであっても、Nextへの呼び出しが先行する必要があります。

sql - The Go Programming Language
[Go言語] database/sqlパッケージを使ってみた - Qiita

QueryRow

QueryRowは、指定された引数を使用して準備されたクエリステートメントを実行します。ステートメントの実行中にエラーが発生した場合、返された* Rowに対するScanの呼び出しによってそのエラーが返されます。これは常に非nilです。クエリが行を選択しない場合、* RowのスキャンはErrNoRowsを返します。それ以外の場合、* Row's Scanは最初に選択された行をスキャンし、残りを破棄します。
sql - The Go Programming Language

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

【Kubernetes Operator】operator-sdkとkubebuilder統合に関する注意点

Operator-sdkのコマンド体系がv0.19.0(2020/7 release)から変わった

Go向けのOperatorのコマンド体系がkubebuilderに沿った形式となったために、既存のコマンドは通らなくなっている。

operator-sdk new osushi-operator --repo github.com/iaoiui/osushi
Error: unknown flag: --repo

operator-sdkのバージョンは下記

❯ operator-sdk version
operator-sdk version: "v0.19.2", commit: "4282ce9acdef6d7a1e9f90832db4dc5a212ae850", kubernetes version: "v1.18.2", go version: "go1.14.5 darwin/amd64"

下記のOperatorframeworkのドキュメントを確認すると、v0.19.0以前はLegacyと呼ばれ別のドキュメントを参照するように指示されていた。

いろいろ変わっている部分はありそうだが、どうやら最初の雛形を作るサブコマンドがnewからinitに変わっているために、—repoというオプションがないというエラーが出ているようだ。

Golang Based Operator Quickstart

そのため、new部分をinitに置き換えることで対応できた。

operator-sdk init osushi-operator --repo github.com/iaoiui/osushi

Operator-sdkのリリース情報をみると、2020/7にv0.19.0がリリースされているためほぼすべての書籍類において、この部分の読み替えが必要になると思われる。

https://github.com/operator-framework/operator-sdk/releases/tag/v0.19.0

Operator-sdkとkubebuilderのGo-based Operato統合の背景

Kubernetes Operatorを作成するためのフレームワークとして大きく、operator-sdkとkubebuilderの2つがありそれぞれ異なるコミュニティによりメンテナンスされてきた。

両方のツールではGoを用いたOperatorのscaffolding(テンプレート生成)が可能であり、作成されるディレクトリやファイル群はは違うものの実現できる目的としては同じであった。

両方のツールで同じようなことをしていることもあり、そのメンテナンス体系を統合することでメンテナンスコストを減らし、開発スピードをあげることが、モチベーションとしてあがってきた。

具体的な方法としては、operator SDK側にはGo Operatorに関する様々な機能があるため、operator SDK側のコントリビュータがそれらの機能を、kubebuilder側にマージしていく作業を先導していく。

つまり、kubebuilder側をアップストリームとし、operator sdk側はkubebuilderのラッパーとして動作するというような、若干の政治の匂いがする形で決着したのだと推測される。

またドキュメントに関しても、operator sdk側のドキュメントをkubebuilder側にマージしていき、kubebuilder側をメンテしていくとのこと。

上記に記載した内容は、kubebuilder側の設計資料に記載されている内容を意訳したものである。

kubebuilderとoperator-sdkの統合に関するkubebuilderのデザインドキュメント

Operator-sdkのCLI体系変更に関するPR

operator-sdkのPR(#3190)で示されている通り、Go向けのOperatorはKubebuilder CLIに沿った形になったとのこと。
Untitled.png

そのために、0.19.0からコマンド体系が変わり、以前のコマンド体系はLegacyになったと考えられる。

特に注意したいのは、operator-sdkコマンドで生成されるファイル群が、全体的に変わってしまっておりLegacyなoperator-sdkを一度忘れた方が話が早いレベルのため、Operatorをこれから触ろうとする人にとっては、現存の書籍を読むのは辛くなると思われる。

特定の書籍名を出すのは避けた方が無難だが、下記の書籍を昨日ポチったので統合に関する背景を噛みしめながら読む。
実践入門 Kubernetesカスタムコントローラーへの道
ステマ感が増すが、Kubernetes完全ガイド第2版もなつやすみの課題図書として読む。

まとめ

  • Kubernetes Operatorを使う場面がさらに広まっていく中で、同様のフレームワークが統合されることで初学者がツール選びに悩む時間が軽減される流れはかなり好ましい状況と思える。
  • 正確には、Go-basedのoperator作成部分の統合であるため完全な統合ではないが、 実質operator-sdk>kubebuilderという機能関係になるため、operator-sdk側を使用させたいという狙いが透けて見える。
  • ともかく、ユーザサイドとしてはコマンド体系がガチャガチャ変わると困るので、今後も動きを注視していきたい。

kubebuilderとoperator-sdkの統合に関する進捗状況やマイルストンを確認したい。
また、デザインドキュメントに記載されていることと、実際のPRがでていることから、ある程度統合が進んでいると思ってはいるが
実際のところどのような課題がありどのように落ちつくのか、さらに整理が必要。

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

【GoでIPv6】GoネットワークプログラムでIPv6対応させるためのアドレスファミリーチェックをする方法

理由

Goで書かれたネットワークプログラムをIPv6に対応させるためには、サーバサイドはあまり意識しなくてもいいが、アドレス処理を文字列で処理する事がある場合、そのアドレス文字列(ログやサーバの環境変数で見えるクライアントアドレス)がどちらのアドレスファミリーなのかチェックをし、それぞれの処理をする必要がある
その場合のコード例をメモしておく

参考コード

main.go
package main

import (
    "fmt"
    "net"
)

// アドレスファミリーチェック関数
func afCheck(addr string) string {
    // 文字列をnet.IPにParse
    ip := net.ParseIP(addr)

    // Parseできたら
    if ip != nil {
        if len(ip.To4()) == net.IPv4len {
            // アドレスの長さをチェックし、4byte=32bit -> IPv4
            return "ipv4"
        } else if len(ip.To16()) == net.IPv6len {
            // アドレスの長さをチェックし、16byte=128bit -> IPv6
            return "ipv6"
        } else {
            // 未知の場合
            return "unknown"
        }
    } else {
        // Parseできない場合
        return "invalid"
    }
}

// メイン関数
func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256","::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // afCheckの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, afCheck(v))
    }
}

解説

ip.To4(),ip.To16()がどのような動きをするか詳細を見てみると

    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        ip := net.ParseIP(v)
        fmt.Printf("%d: %s %#v(%d) %#v(%d)\n", i, v, ip.To4(), len(ip.To4()), ip.To16(), len(ip.To16()))
    }

上記結果を整形すると

i v ip.To4() len(ip.To4()) ip.To16() len(ip.To16()
0 2001:db8::1 net.IP(nil) 0 net.IP{0x20, 0x1, 0xd, 0xb8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1} 16
1 192.168.0.1 net.IP{0xc0, 0xa8, 0x0, 0x1} 4 net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xc0, 0xa8, 0x0, 0x1} 16
2 ffff:: net.IP(nil) 0 net.IP{0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} 16
3 ::1 net.IP(nil) 0 net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1} 16
4 12345 net.IP(nil) 0 net.IP(nil) 0
5 192.168.1 net.IP(nil) 0 net.IP(nil) 0
6 192.168.1.256 net.IP(nil) 0 net.IP(nil) 0
7 ::192.0.2.1 net.IP(nil) 0 net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0, 0x0, 0x2, 0x1} 16

これにより、有効なIPアドレスの場合、Parseが成功し

  • IPv4の場合「ip.To4()」に値が入る。(この場合、「ip.To16()」にも値がある。これはIPv4射影アドレス(IPv4-mapped IPv6 address)となっている点注意が必要)
  • IPv6の場合「ip.To4()」に値が入らず、「ip.To16()」に値が入る。

アンチパターン

やってしまいがちなアンチパターンは文字列判断してしまうこと。ここでは単純な判断にしているが、「:」があって「.」がないとか、「:」の数を数えたり、正規表現判断することも同じアンチパターンと考える。
- https://play.golang.org/p/8mNeoxGyxLb

main.go
package main

import (
    "fmt"
    "strings"
)

// アドレスファミリーチェック関数
func afCheck(addr string) string {
    if strings.Contains(addr, ".") {
        // アドレスの文字列から「.」を含んでいたらIPv4と判断
        return "ipv4"
    } else if strings.Contains(addr, ":") {
        // アドレスの文字列から「:」を含んでいたらIPv6と判断
        return "ipv6"
    } else {
        // 未知の場合
        return "unknown"
    }
}

// メイン関数
func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // afCheckの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, afCheck(v))
    }
}

まとめ

アドレスファミリーを文字列で判断するのはNG

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

【GoネットワークプログラムでIPv6対応させる】ログ等のアドレス文字列からアドレスファミリー判別をする方法

理由

Goで書かれたネットワークプログラムをIPv6に対応させるためには、サーバサイドはあまり意識しなくてもいいが、アドレス処理を文字列で処理する事がある場合、そのアドレス文字列(ログやサーバの環境変数で見えるクライアントアドレス)がどちらのアドレスファミリーなのかチェックをし、それぞれの処理をする必要がある
その場合のコード例をメモしておく

参考コード

main.go
package main

import (
    "fmt"
    "net"
)

// アドレスファミリーチェック関数
func afCheck(addr string) string {
    // 文字列をnet.IPにParse
    ip := net.ParseIP(addr)

    // Parseできたら
    if ip != nil {
        if len(ip.To4()) == net.IPv4len {
            // アドレスの長さをチェックし、4byte=32bit -> IPv4
            return "ipv4"ß
        } else if len(ip.To16()) == net.IPv6len {
            // アドレスの長さをチェックし、16byte=128bit -> IPv6
            return "ipv6"
        } else {
            // 未知の場合
            return "unknown"
        }
    } else {
        // Parseできない場合
        return "invalid"
    }
}

// メイン関数
func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256","::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // afCheckの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, afCheck(v))
    }
}

解説

ip.To4(),ip.To16()がどのような動きをするか詳細を見てみると

    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        ip := net.ParseIP(v)
        fmt.Printf("%d: %s %#v(%d) %#v(%d)\n", i, v, ip.To4(), len(ip.To4()), ip.To16(), len(ip.To16()))
    }

上記結果を整形すると

i v ip.To4() len(ip.To4()) ip.To16() len(ip.To16()
0 2001:db8::1 net.IP(nil) 0 net.IP{0x20, 0x1, 0xd, 0xb8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1} 16
1 192.168.0.1 net.IP{0xc0, 0xa8, 0x0, 0x1} 4 net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xc0, 0xa8, 0x0, 0x1} 16
2 ffff:: net.IP(nil) 0 net.IP{0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} 16
3 ::1 net.IP(nil) 0 net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1} 16
4 12345 net.IP(nil) 0 net.IP(nil) 0
5 192.168.1 net.IP(nil) 0 net.IP(nil) 0
6 192.168.1.256 net.IP(nil) 0 net.IP(nil) 0
7 ::192.0.2.1 net.IP(nil) 0 net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0, 0x0, 0x2, 0x1} 16

これにより、有効なIPアドレスの場合、Parseが成功し

  • IPv4の場合「ip.To4()」に値が入る。(この場合、「ip.To16()」にも値がある。これはIPv4射影アドレス(IPv4-mapped IPv6 address)となっている点注意が必要)
  • IPv6の場合「ip.To4()」に値が入らず、「ip.To16()」に値が入る。

アンチパターン

やってしまいがちなアンチパターンは文字列判断してしまうこと。ここでは単純な判断にしているが、「:」があって「.」がないとか、「:」の数を数えたり、正規表現判断することも同じアンチパターンと考える。
- https://play.golang.org/p/8mNeoxGyxLb

main.go
package main

import (
    "fmt"
    "strings"
)

// アドレスファミリーチェック関数
func afCheck(addr string) string {
    if strings.Contains(addr, ".") {
        // アドレスの文字列から「.」を含んでいたらIPv4と判断
        return "ipv4"
    } else if strings.Contains(addr, ":") {
        // アドレスの文字列から「:」を含んでいたらIPv6と判断
        return "ipv6"
    } else {
        // 未知の場合
        return "unknown"
    }
}

// メイン関数
func main() {
    // チェック対象のアドレスをスライスに入れる
    addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256", "::192.0.2.1"}

    // スライスを1つづつチェック
    for i, v := range addrs {
        // afCheckの返答を確認
        fmt.Printf("%d: %s = %s\n", i, v, afCheck(v))
    }
}

まとめ

アドレスファミリーを文字列で判断するのはNG

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

Docker Vue Go

Docker

Dockerマウントできないよ〜

ERROR: for nginx  Cannot start service nginx: OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/Users/ishidashogo/practice/goPractice/etc/nginx/nginx.conf\\\" to rootfs 
\\\"/var/lib/docker/overlay2/ac829670cc63c6bd9749802860c0b6dd9619d56f1883525650acc81db4dc4ee1/merged\\\" at \\\"/var/lib/docker/overlay2/ac829670cc63c6bd9749802860c0b6dd9619d56f1883525650acc81db4dc4ee1/merged/etc/nginx/nginx.conf\\\" caused \\\"not a directory\\\"\"": unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type

マウントのディレクトリが違うみたいでした。
docker-compose.ymlファイル内で指定しているvolumeの場所に指定してある
欲しいファイルがないことによるエラーみたいでした。

https://qiita.com/hanakok/items/6bfcfbb8b7b4df877178

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