それが僕には楽しかったんです。

僕と MySQL と時々 MariaDB

PHPerがいくGolang入門 A Tour of Go Flow control statements: for, if, else, switch and defer

はじめに

Muroran Institute of Technology Advent Calendar 2018 20日目の記事です。

どうも、2018/12/20という未来から来ましたけんつです。
引き続き A Tour of Go を進めていこうと思います。

ただ昨日、気がついてしまったのですがこのあたりの構文系の解説を日本語で書くまでもないので
今回はひたすらコードで語っていこうと思います。

難解な部分だけ、適宜解説はさみます。

そして今回も全くPHP関係ないです。

For

package main;

import "fmt";

func main() {
    sum := 0;
    for i:= 0; i < 10; i++ {
        sum += i;
    }
    fmt.Println(sum);
}
package main;

import "fmt";

func main() {
    sum := 0;
    for i:= 0; i < 10; i++ {
        sum += i;
    }
    fmt.Println(sum);

    for sum < 1000 {
        sum += sum;
    }
    fmt.Println(sum);
}
||
初期化と後処理ステートメントを省略できて、こうなるとwhile文と同様の扱いとなる

* If

>|go|
package main;

import "fmt";
import "math";


func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i";
    }
    return fmt.Sprint(math.Sqrt(x));
}

func main() {
    fmt.Println(sqrt(2), sqrt(-4));
}

このようにかける。
if 文より気になったのがsqrt関数の末尾でreturnしている部分、ここを return string(***);とすると
convertできない旨が帰ってきた。


続いて、評価式の前に簡単なステートメントがかけるらしいということがわかった。
ただし、そこで宣言された変数などはif~elseのスコープ内でしか使えない。

package main;

import "fmt";
import "math";


func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i";
    }
    return fmt.Sprint(math.Sqrt(x));
}

func pow(x float64, n float64, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v;
    }
    return lim;
}

func main() {
    fmt.Println(sqrt(2), sqrt(-4));
    fmt.Println(pow(3, 2, 10), pow(3, 3, 20));
}

Switch

package main;

import "fmt";
import "runtime";

func main() {
    fmt.Print("Go runs on ");

    switch os := runtime.GOOS; os {
    
        case "darwin":
            fmt.Println("OS X.");
        case "linux":
            fmt.Println("Linux.");
        default:
            fmt.Printf("%s.", os);
    }

}

これも普段となんら変わらないけど、特徴としてbreakが自動的に提供されているため
明示的に記述する必要がない。

もう少し正確に書くなら、条件にマッチした部分で自動的にbreakされる。

defer

きたこれ案件。
deferは、渡した関数の実行を実行している関数が終了するまで遅延させる。

package main;

import "fmt";


func main() {
    defer fmt.Println("world");
    fmt.Println("hello,");
}

2つ以上の関数をdeferに渡すとそれはスタックされるので LIFO の順番で処理される
>|go|package main

import "fmt"

func main() {
fmt.Println("counting")

for i := 0; i < 10; i++ {
defer fmt.Println(i)
}

fmt.Println("done")
}
|

$ go run multi_defer.go 
done
9
8
7
6
5
4
3
2
1
0

おわりに

今回は一般的な制御構文ですごくさくっと、なにも書くことなく終わってしまったので
次回以降はもうちょい骨のあることしたい。

PHPerがいくGolang入門 A Tour of Go Basics, Packages, variables, and functions

はじめに

どうも、2018/12/19という未来からきたけんつです。
この記事は、Muroran Institute of Technology Advent Calendar 2018 19日目の記事です。

なんかクリスマスも近いのでGolang入門やろうと思います。
今回は基本文法周り攻めれたらなと思っています。

A Tour Of Goを見ながら進めていきます。
基本的な四則演算等は省略します。

環境

Ubuntu 16.04
go version go1.6.2 linux/amd64

Hello,World

goはpackage単位でプログラムを組むらしい。
もっと言うならば、mainパッケージのmain関数から実行していく。

package main;

import "fmt";

func mainsub() {
    fmt.Println("hello,world");
}


こんな感じで。
セミコロンは省略できるが個人的に書いていきたいから書く。

ちなみに関数名をmain以外にすると次のように怒られる。

# command-line-arguments
runtime.main: call to external function main.main
runtime.main: main.main: not defined
runtime.main: undefined: main.main

package.関数で探しているっぽい。

上記のファイルを hello.go としたとき、以下の様に走らせると標準出力がでてくる。

$ go run hello.go 
hello,world
||< 

そして、この先頭でインポートしているパッケージ群にフォーマットI/Oに必要な関数群があるので
基本的にこれはインポートすることになる。

* 複数パッケージのインポートとパッケージ内の関数参照

Hello,worldの時点で色々と個人的に謎が深まったので一番気になった、パッケージの複数インポートとパッケージ内の関数参照についてまとめていく。


** 複数のパッケージインポート

次の様に出来る 
>|go|
import "fmt";
import "math";

次の様にまとめることも出来る。

import (
    "fmt"
    "math"
);

パッケージ内の参照

次の様に、パッケージ名.メンバで参照できる(メンバと言って良いのかわからないけど)

package main;


import (
    "fmt"
    "math"
);


func main() {
    fmt.Println(math.Pi);
}
$ go run multi_package.go 
3.141592653589793

関数

次に関数について、触れていく。
関数は例によって0個以上の引数を取ることができる。
そして戻り値を返すこともできる。

何やら型でintが存在するようなので早速書いてみる。

package main;

import "fmt";

func add(x int, y int) int {
    return x + y;
}

func main() {
    fmt.Println(add(1, 3));
}
$ go run functions.go 
4

コードを見てもらうとわかるように、引数名の後に型名をおいているScalaのように。
戻り値の型も宣言できている。returnも使える。

関数宣言は呼び出しより後ろにあってもエラーにならずコールされる。

package main;

import "fmt";

/*func add(x int, y int) int {
    return x + y;
}*/

func main() {
    fmt.Println(add(1, 3));
}
func add(x int, y int) int {
    return x + y;
}
$ go run functions.go 
4

また、型が同じなら次のように省略できる。

func add(x, y int) int {
    return x + y;
}

これはあまり好きではないので、これからは省略できても一個一個型を書いていく。

複数の結果を返す

これは驚きの機能で複数の結果をreturnすることができる。
すごく簡単で、以下のようにする。

package main;

import "fmt";

func main() {
    fmt.Println(add(1, 3));

    a, b := swap("hello", "world");
    fmt.Println(a, b);
}

func add(x, y int) int {
    return x + y;
}

func swap(a string, b string) (string, string) {
    return b, a;
}
$ go run functions.go 
4
world hello

これ便利。

名前付きreturn

これも地味に便利だとおもった。
次のコードの様に、戻り値に名前を付けて返すことが出来る。

package main;

import "fmt";

func main() {
    fmt.Println(split(17));
}

func split(sum int) (x int, y int) {
    x = sum * 4 - 9;
    y = sum - x;
    return;
}
$ go run name_return.go 
59 -42

変数宣言

var を使って変数を宣言する

package main;

import "fmt";

var c, python, java bool;

func main() {
    var i int;
    fmt.Println(i, c, python, java);
}
$ go run variables.go 
0 false false false

構文としては 「var 変数名 型名;」となっている。
何も変数に代入しない場合はデフォルトの値が設定される。

変数初期化

初期化は初期化子を使うことが宣言時に出来る。
これによって初期化された場合、型名は省略できる。

package main;

import "fmt";

var c, python, java bool = true, false, true;

func main() {
    var i = 10;
    fmt.Println(i, c, python, java);
}
$ go run variables.go 
10 true false true

短縮宣言

varを使う代わりに、:=を使い暗黙的な型宣言を行うことができる。
しかし、この構文は関数の外で使うことができない。

package main;

import "fmt";

var c, python, java bool = true, false, true;

func main() {
    var i = 10;
    fmt.Println(i, c, python, java);

    scala, php := true, false;
    fmt.Println(scala, php);
}
$ go run variables.go 
10 true false true
true false

基本型は次の通り

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名
     // Unicode のコードポイントを表す

float32 float64

complex64 complex128

型変換

キャストに近いことができる。
利用方法は簡単で「形名(変数or値)」とするだけ。

おわりに

楽しい。

Docker Remote APIを使いたかった。

はじめに

最近マッサージ機を使っている時にふくらはぎのツボを見つけた、けんつです。

この時期は、Muroran Institute of Technology Advent Calendar 2018 16日目の記事です。

今取り組んでいるプロジェクトで、最近話題のk8sを使わずにdockerコンテナをいい感じに制御する必要があり 「Docker Remote API」 なるものを使い倒しているので今回はその概要と利用方法をざっくりとまとめようかなと思っています。

具体的な例は後日、別日のアドカレで書きます。

Docker の 基本的な仕組み

Dockerの仕組みといっても、大体は以下の記事に書いてあることを中心に特に Docker Remote API に関することを書きます。
というか、なぜ Docker Remote API があるのか。みたいなことを話すのに Docker の超基本的な仕組みを紹介する必要があるからです。

docs.docker.com


Docker は以下のようなクライアント-サーバアーキテクチャを採用している。

f:id:RabbitFoot141:20181215214310p:plain


これらは主要な3つのコンポーネントによって成立している。

  • デーモンプロセス:サーバの役割を果たす
  • REST API:クライアントとサーバの仲立ちの役割を果たす。
  • CLIクライアント:文字通りクライアントの役割を果たす。

よく、 docker ps などコマンドを使うと思うがこれはコマンドがクライアントの役割を担っていて Docker Remote API を叩くことで Docker Daemon が処理を行うという構成になっている。

Docker の アーキテクチャ


基本的な仕組みは上で解説したとおりで、次は重要となるアーキテクチャ部分を詳解する。

Docker のアーキテクチャは以下のようになっている。

f:id:RabbitFoot141:20181215215123p:plain


それぞれの役割を以下にまとめる。

Docker Daemon

Docker Daemon(dockerd) は Docker APIコール を listen して、コンテナ、ボリューム、ネットワークなど Docker で必要となるオブジェクトを管理する。
そしてこの Daemonは他のDaemonと通信することも出来る。

Docker Client

Docker Client(docker command) はユーザが Docker を使用する上で主な方法と成る。
これらは Docker API をコールして Docker Daemon の機能を利用している。

Docker Registries

Docker Registries は Docker Image を管理している。特によく使用する Docker Hub は誰もが利用できる公開レジストリでデフォルトではこの Docker Hub を Docker は参照するようになっている。
docker pull, docker run などで必要なイメージがレジストリから取得される。

Docker Remote API とは

上記の説明でわかったと思うが、実際に docker コマンドなどがコールしているAPIがそれに当てはまる。
Docker コマンドは単にクライアントにすぎないのでDocker コマンドでなくてもこのAPIをコールすることができ、コンテナの作成削除、はたまたコンテナ内の標準入出力なども取得することができる。

ここでは、その利用方法と簡単な例をあげようと思う。

準備

Docker Remote APIはそのまま使うことはできない。
というのも、デフォルトでは Unix Domain Socketでのみ利用可能になっているからだ。


まず、Docker Remote API を利用する場合に Unix Domain Socket だけでなく TCP を使えるようにする必要がある。


Ubuntu の場合は /etc/init/docker.conf に

DOCKERD=/usr/bin/dockerd
DOCKER_OPTS="-d -H=tcp://127.0.0.1:4243"

とある部分の、 DOCKER_OPTSで 127.0.0.1 、 4243ポートでTCPを許可する一文をいれてからデーモンを再起動することで使えるようになる。
TCPでいけるということは curl などを使ってHTTPリクエストでいけるということになる。


これで行けない場合が存在するらしいがその時は /lib/systemd/system/docker.service で

ExecStart=/usr/bin/dockerd -H unix:// -H tcp://0.0.0.0:4243

とするといけるそう。

実際のシステムで運用する際などには、これではちょっとまずいことになるので証明書などを導入するべき。
Protect the Docker daemon socket | Docker Documentation

そこら辺が面倒ならUnix Domain Socketを利用するとよいかもしれない。

これで準備は完了した。

利用例

利用例の一部を紹介する。

コンテナ一覧を取得

試しに、redisコンテナを起動させた状態でやってみた。

$ curl http://127.0.0.1:4243/containers/json
[
	{
		"Id": "249a982a42823380db8703966915dca91548f945bb109beaff31926f85d007a8",
		"Names": [
			"/accepted_redis_1_b573c8ffd990"
		],
		"Image": "redis:latest",
		"ImageID": "sha256:c188f257942c5263c7c3063363c0b876884eb458d212cf57aa2c063219016ace",
		"Command": "docker-entrypoint.sh redis-server --appendonly yes",
		"Created": 1544365476,
		"Ports": [
			{
				"IP": "0.0.0.0",
				"PrivatePort": 6379,
				"PublicPort": 6379,
				"Type": "tcp"
			}
		],
		"Labels": {
			"com.docker.compose.config-hash": "69b24a42ee98408e3e9622311382cc47736de52c1bdc8a578731e06673db2f26",
			"com.docker.compose.container-number": "1",
			"com.docker.compose.oneoff": "False",
			"com.docker.compose.project": "accepted",
			"com.docker.compose.service": "redis",
			"com.docker.compose.slug": "b573c8ffd9903731bccd479f3086b206bee4d6ac4e66e89da012e25b21743bb",
			"com.docker.compose.version": "1.23.0"
		},
		"State": "running",
		"Status": "Up 6 days",
		"HostConfig": {
			"NetworkMode": "accepted_default"
		},
		"NetworkSettings": {
			"Networks": {
				"accepted_default": {
					"IPAMConfig": null,
					"Links": null,
					"Aliases": null,
					"NetworkID": "da9a9b579158911ba56881af6149f37f2a9a7cc35cf2348183ddf9db75642cab",
					"EndpointID": "5a4ea6f575af66bb0ee307dcc82ea6aeb85517f5daf3a02e58f447336ad75631",
					"Gateway": "172.18.0.1",
					"IPAddress": "172.18.0.2",
					"IPPrefixLen": 16,
					"IPv6Gateway": "",
					"GlobalIPv6Address": "",
					"GlobalIPv6PrefixLen": 0,
					"MacAddress": "02:42:ac:12:00:02",
					"DriverOpts": null
				}
			}
		},
		"Mounts": [
			{
				"Type": "volume",
				"Name": "d5c83c863897ed4a0f6bab5dc55e56744fb5edf3a284fcae0e7161f61b1670aa",
				"Source": "",
				"Destination": "/data",
				"Driver": "local",
				"Mode": "",
				"RW": true,
				"Propagation": ""
			}
		]
	}
]
コンテナを作成
$ curl -X POST -H "Content-Type: application/json" http://localhost:4243/containers/create -d '{"Hostname":"","User":"","Memory":0,"MemorySwap":0,"AttachStdin":false,"AttachStdout":true,"AttachStderr":true,"PortSpecs":null,"Privileged": false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Dns":null,"Image":"nginx","Volumes":{},"VolumesFrom":[],"WorkingDir":""}'

{"Id":"2bdb551799ccff923a417dac1135c190dcb16b9429eca9cb9eb5daffd4a69751","Warnings":null}

これで起動すると docker ps ではコンテナが見えないらしく docker ps -a で全てのコンテナを参照する必要がある。

また手元にイメージのないコンテナは一度 pull 必要がある。

$ docker ps -a
CONTAINER ID        IMAGE                          COMMAND                  CREATED              STATUS                      PORTS                                      NAMES
2bdb551799cc        nginx                          "nginx -g 'daemon of…"   45 seconds ago       Created    
コンテナを止める

さっきたてたコンテナを止める

curl -X POST http://localhost:4243/containers/2bdb551799ccff923a417dac1135c190dcb16b9429eca9cb9eb5daffd4a69751/stop

これで止まる。コンテナIDではなくコンテナ名でもよさげ

おわりに

Docker Remote APIを使えばこれ以外にも色々できるため、リクエストに応じてコンテナを立てたり消したりするオンラインジャッジシステムの一部が組めるようになるのではと思っている

サポーターズさんの逆求人にいってきたら色々変わった話

はじめに

どうも、最近忙しい忙しい言っているけど何故か映画見る時間だけはちゃんと確保できているけんつです。


いろいろあって、10/27,28に開催された「エンジニア 1on1 面談会」に参加したら
がらっと自分の思っていたことが変わったのでそれについて書こうと思います。

supporterz.jp

何故参加することになったか

そろそろ、就活時期だなと思っていたけど何の準備もしていなかったため参考程度にサポーターズさんのサイトを見に行ってみたら

「就活は年内にピークを迎える企業もあります」

とあり、実際の就活って一括なら4月から5月で決まっているという印象があったのでそのへんをサポーターズの中の人に聞いてみました。
そしたら、よく名前を聞くような企業さんは通年採用で本当に年内がピークだとのことで業界の事情を知るために急遽参加することになりました。

参加するまでの話

参加するまでは受託に行きたいなと思っていました。
というのも、その直前にインターンAWSを触っていたらかなり楽しくて、いろんな現場にいってそういったことを楽しみたいなと思っていたからです。

それに今まで自分は様々な分野をかじったりしていて、特にどのレイヤーや分野が好きというわけではなく何でも面白いなと思っていたのも関係しています。
そのあたりは以前書いた記事を見てもらえればと思います。

rabbitfoot141.hatenablog.com


それらを踏まえた上でコード書くのが好きでずっと現場で開発に携われるようなキャリアパスを考えていました。


そんなこんなで、参加したイベントでは自社サービスを展開している企業さんが多かったので

「きっと考え方が合わないだろうな」
「まぁ行くだけ行くか、それでもきっと受託行きたいっていう思いは変わらんだろうな」
「きっと、イケイケなひと多くて、今まで地味なことやってきた自分は評価されんだろうなぁ」


とか色々思っていました。正直なところ。マジで。

当日話すスライドの準備

面談会では参加している企業さんと25分面談する時間が与えられ、最初の5分間で軽く自己紹介をする時間が与えられます。
そのためそこで自己紹介するためのスライドを準備する必要がありました。

5分では語り尽くせないので、さくっとまとめるために以下のことを書いて持って行きました。

  • やっていること
    • 今やっているシステム構築の話
  • 今後叶えたいキャリアパス

イベントが始まるまで

当日、会場に向かうとそれはそれはもう雰囲気から出来るオーラの漂う学生達が大勢いて完全に萎縮しました。まじで。
面談会の始まるまでの時間で胃が痛くなる程に。

胃痛は、一社目の面談おわった段階で日頃からお世話になっているサポーターズのさりーさんが自分のいる机までやってきて


こんな感じに雑に絡んできてくれたので吹っ飛びました笑

面談会では…


面談会では、それまで想像していた自分のイメージとは全く逆の反応が全部で
特に、パケットを書いた話やカーネルモジュールを書いていた話をしたら良い反応が頂けて本当に意外でした。



そして多くの企業さんから

「君、面白いことやっているね」
「そのスキルを活かせる場所、うちならあるよ」

と、言って頂けて本当に嬉しかったです。


それに話せば話す程、「うちの会社はこういう事をやっていてこういう制度もある」などと熱心に教えて頂いたり
個人的に興味を持っていただいたエンジニアの方とお話するのもすごく楽しかったです。

そんなこんなで、「絶対自社サービス展開している企業に行かない!!」と言い張っていた自分も色々と話をしているうちに
自社サービスでも色々なことに携われるということを知り、一気に自社サービス側に傾いて行きました。


さらにさらに、話していくうちに自分がプラットフォーム開発に向いているという意見も多く頂き
自分が向いていると今まで思っていなかった分野に特性があることを知れたことも本当によかったです。(これは本当に意外だった)


この、今まで自分がやってきたことが評価された点と、業界についての感覚が変わった点が面談会での一番大きな収穫となりました。

懇親会で…

懇親会は、お酒とピザやお寿司がならぶ中でかなりフランクな感じでエンジニアの方々や人事の方々とお話する重要な機会でした。
ただあまり特定の人と話してしまうと、他の方が離せなくなってしまうとのことで15分で席替えがあるといった感じでした。

その懇親会のなかで、特に嬉しかったのが一日目と二日目でそれぞれ懇親会が始まるとすぐ

「けんつくんだよね?さっき面談した人から評判は聞いているよ、それで是非話しておかないとと思ったから真っ先にきたよ笑」

と、言ってくださった企業さんがいたことです。
今まで自分を「自分はその他大勢の中の一人で、目立つような人間ではない」と思っていたので、この一言は衝撃でした。

うまい言い回しが思いつきませんが、これは本当に嬉しかったです。

1on1面談イベントを終えて


この面談イベントは本当にハードで、二日間合計で16社(?、あまり覚えていない)と面談し半日ほどしゃべりっぱなしで声がガラガラになったり
あと、シンプルに疲れます。笑


ただ、このイベントに参加して自分の向いている分野や今後の方針、価値観などが大きく変わりました。
いつも自分のことは自分が一番良くわかっていると思っていましたが、自分の知らない自分のことが知れた非常に良い機会となりました。


このイベントに参加するにあたって、色々とサポートして頂いたサポーターズのさりーさん、いっちーさん本当にありがとうございました。

後日談

このイベントの後にフィードバックが送られてくるのですが、それらを一部抜粋してがんがん意訳して企業名を伏せた上で紹介しようと思います。
自分は主にこういったフィードバックが多かったです、似たようなタイプの人の参考になればと思います。

「技術力について申し分ないが、特に技術に向き合う姿勢が非常によかった」
「技術にこだわりを持っているところがよい」
「自分が作ったものを心底楽しそうに話す姿勢が技術者として良かった」
「技術に関する探求力と手を動かしてしまうところが良かった」
「幅広いレイヤ興味。学習速度。意欲等素晴らしかった」


と、技術力も一定水準を満たしていると評価して頂きましたがその他で特に技術に対する向き合い方を評価していただきました。
これで満足するのではなく、「これはこの時期のこの程度の学生にしては」と思って更に技術力を磨いていきたいと思っています。


本当に、行ってよかった。

Dockerコンテナ内から外部通信が向いたときにDNSが解決できない問題を解消する

はじめに

どうも、最近海外ドラマで特にNetflix系のドラマにドハマりしているけんつです。

最近、Laradockを使うときにコンテナをビルドしたらDNSが解決できない問題に苦しめられていました。
なんかすごく詰まったのにあっさりと解決してしまったのでまとめます。

環境

  • Ubuntu16.04
  • Docker version 18.09.0, build 4d60db4
  • docker-compose version 1.23.0, build c8524dc1

問題

Laradockで色々とコンテナをビルドしていた時にこんなエラーが頻発して全然ビルドできなかった

Step 3/12 : RUN apk --update add wget   curl   git   build-base   libmemcached-dev   libmcrypt-dev   libxml2-dev   zlib-dev   autoconf   cyrus-sasl-dev   libgsasl-dev   supervisor
 ---> Running in 46add29caab7
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.8/main: temporary error (try again later)
WARNING: Ignoring APKINDEX.adfa7ceb.tar.gz: No such file or directory
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.8/community: temporary error (try again later)
WARNING: Ignoring APKINDEX.efaa1f73.tar.gz: No such file or directory
ERROR: unsatisfiable constraints:
  autoconf (missing):
    required by: world[autoconf]
  build-base (missing):
    required by: world[build-base]
  cyrus-sasl-dev (missing):
    required by: world[cyrus-sasl-dev]
  git (missing):
    required by: world[git]
  libgsasl-dev (missing):
    required by: world[libgsasl-dev]
  libmcrypt-dev (missing):
    required by: world[libmcrypt-dev]
  libmemcached-dev (missing):
    required by: world[libmemcached-dev]
  libxml2-dev (missing):
    required by: world[libxml2-dev]
  supervisor (missing):
    required by: world[supervisor]
  wget (missing):
    required by: world[wget]
  zlib-dev (missing):
    required by: world[zlib-dev]
ERROR: Service 'php-worker' failed to build: The command '/bin/sh -c apk --update add wget   curl   git   build-base   libmemcached-dev   libmcrypt-dev   libxml2-dev   zlib-dev   autoconf   cyrus-sasl-dev   libgsasl-dev   supervisor' returned a non-zero code: 11

なぜかfetchできないが、普通にターミナルからcurlなりwgetすると取得できたからdockerコンテナ 内部でDNSを解決できていないのかと考えていた。

それとworkspaceのビルドする際にcomposerを走らせる段階でこんなエラーもでる

Changed current directory to /home/laradock/.composer
Loading composer repositories with package information

                                                                                                                                                              
  [Composer\Downloader\TransportException]                                                                                                                    
  The "https://packagist.org/packages.json" file could not be downloaded: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution  
  failed to open stream: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution                    


composer diagをしてみてもこの通り

Checking platform settings: OK
Checking git settings: OK
Checking http connectivity to packagist: WARNING
[Composer\Downloader\TransportException] The "http://packagist.org/packages.json" file could not be downloaded: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution
failed to open stream: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution
Checking https connectivity to packagist: WARNING
[Composer\Downloader\TransportException] The "https://packagist.org/packages.json" file could not be downloaded: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution
failed to open stream: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution
Checking github.com rate limit: FAIL
[Composer\Downloader\TransportException] The "https://api.github.com/rate_limit" file could not be downloaded: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution
failed to open stream: php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution
Checking disk free space: OK
Checking pubkeys: FAIL
Missing pubkey for tags verification
Missing pubkey for dev verification
Run composer self-update --update-keys to set them up
Checking composer version: 


完全にご臨終してしまう

解決策

qiita.com

↑これをやったら想定した動作をした。

実際には全ておこなったわけではなくて


「dockerのホストであるUbuntuでは、デフォルトでdnsmasq が有効になっていて、 /etc/resolv.confが下記のように自分を見る設定になっている。」
この部分を見て /etc/resolv.conf に

nameserver 8.8.8.8
nameserver 8.8.4.4

を追記したて Docker を再起動するだけで、DNSでコケることがなくなった

おわりに

いつからこうなったんだ…

Nagleアルゴリズムと遅延ACK

はじめに

どうも、最近嫌な予感が段々と確信になっていくタイプのサイコホラー映画に弱いという事がわかったけんつです。


諸事情で、Linuxカーネルモジュールとして実装したEchoサーバのコードを見なおしていたらNagleアルゴリズムと遅延ACKというものに出会いました。
気になったので色々と調べているとその仕組みが面白いなと思ったのでまとめます。

本題に入る前に

今回紹介することは、ネットワークで特にTCP/IPにおける輻輳制御についてのお話になる。
がしかし、輻輳制御ってなんやねんと思うのでちょっとまずそこに触れておく。

輻輳輻輳制御とは

輻輳とは、「さまざまな物が1カ所に集中すること」*1 を指している。
ネットワークの分野に置いては「ラピュタ金曜ロードショーで放送されたときTwitterバルスが流れまくる」ときの様に一箇所にアクセスが集中するときが輻輳のいい例となっている。

対して、輻輳制御とは輻輳が発生しそうになった場合にそれを制御することを指している。インフラにおいては、ロードバランサーがいい例として挙げられる。

Nagleアルゴリズムとは

ネーグルアルゴリズムと言うみたい。
RFC 896「IP/TCPインターネットワークにおける輻輳制御」*2で制定されており、その名の通り輻輳制御に関するアルゴリズムである。
そもそも、これが何故必要かということはある問題がキーになっているとRFCには記載されている。

The small-packet problem

この問題は、アプリケーションが1byte 程のサイズでパケットを送ってしまう際に発生する問題を指している。
何が問題かといえば、IPv4TCPのそれぞれでヘッダーサイズが20byteあるため 1byte ほどのメッセージを送信するために 41byte のパケットを送信する点にある。
このヘッダーに対して明らかに大きすぎるパケットを送信してしまう現象は、 Telnetセッションでよく見られるらしく通信速度の遅いネットワークを圧迫する。

The small-packet problem の解決法としてのNagleアルゴリズム

この問題を解決するためにNagleアルゴリズムは存在している。
Nagleアルゴリズムは以下の3つの条件を満たすまで、パケットをバッファにためて条件を満たした段階で送信することで効率的にデータを送信するアルゴリズムである。
つまり、「Nagleアルゴリズムでは最大セグメントサイズ以下の複数の送信メッセージを一つに束ね、まとめて送信する。特に、送信パケットで送信側が ACK を受け取っていないのがある場合、送信するに値するまで送信側はバッファリングを行い、そして、一度にまとめて送信する。」アルゴリズムとなる*3

  • 未送信データが最大セグメントサイズ以上になる
  • 過去の送信パケットで ACK が未受信の物がなくなる
  • タイムアウトになる

このアルゴリズムで注意したい点は送信を遅延させる時間が200~500msであるという点(BDS系は最大200ms)。
これは次にまとめる遅延ACKとの相性が最高に悪く、オンラインゲームやその他のリアルタイム性を要求されるネットワークに於いて
そもそも、Nagleアルゴリズムがレイテンシを犠牲にしている分余計に遅延が発生してしまうという致命的な問題を発生させる。

余談:Nagleアルゴリズムを無効化する

前述の通り、リアルタイム性が要求される場合Nagleアルゴリズムは悪い影響を引き起こす場合がある。
そのため、特にレイヤーの低い分野においてソケットでTCP_NODELAYオプションが提供されている。
これを有効化することで、Nagleアルゴリズムを無効にできる。

遅延ACKの前に…

遅延ACKの前に前提としていくつかTCPについて知らなければならないことがあるのでそっちを先に紹介する。

ウィンドウ

TCPにはウィンドウという概念がある。
これは何かというと、TCPでは送信されてきたデータを一時的にバッファに溜め込んでアプリケーションはそこからデータを取り出し処理していく。
これをウィンドウといい、一度に溜め込める量をウィンドウサイズという。

そしてウィンドウサイズは、送信側が確認応答を待たずに一度に送信できる最大量でもある。

またこのウィンドウサイズはフロー制御によって変更される場合があり常に一定というわけではない。

スライディングウィンドウ

スライディングウィンドウについては以下のサイトがわかりやすくまとまっている。
beginners-network.com

このサイトでは、9000byteのデータを送信したい場合でウィンドウサイズが3000byteの場合を想定している。

まず9000 byteを 1セグメント 1000byte ずつ送信する場合3つのパケットを一度に送信できる。
受信側がACKを送信側に送った場合、3001byte~6000byte分のデータを送信する。

このように、相手側のウィンドウサイズに応じて送信するデータの範囲をずらしていくことをスライディングウィンドウという。

しかし、実際には1000byte送ってスライディングウィンドウすることも可能なのだがそれをしてしまうと1セグメントごとにACKを送る必要があるため
非常に効率が悪くなってしまうという問題が発生する。
これが次の遅延ACKに関連している。

遅延ACK

データを受信した側が即座にACKを返すと、フロー制御が働いて小さいウィンドウサイズが設定される場合がある。
送信側はそれに合わせてデータを送ってしまうと非常に効率が悪くなる。(これはSWS: Silliy WIndow Syndromeと呼ばれている)

そこで、このACKを意図的に送らせて、ネットワークを効率よく利用する方法が挙げられた。
それが遅延ACKとなる。

遅延させる場合は

  • 2 * 最大セグメントサイズのデータを受信するまで確認応答しない。
  • 上記以外の場合は最大200~500ms、ACKを遅延させる。

大体2セグメントごとにACKを送信するように遅延させる場合がおおい。(らしい)

Nagleアルゴリズムと遅延ACK

上記で紹介した、遅延ACKとNagleアルゴリズムが両方働くと遅延が余分に働く場合がある
特にサーバが遅延ACKのタイムアウトになるまでリクエストがストップしてしまうという場合。*4

これは以下のサイトにわかりやすい例が記載されている。
postd.cc

特にわかりやすい例を以下にあげる

アプリケーション: やあ、パケット1だよ。
HAProxy:<無反応で、2つ目のパケットを待っている>
HAProxy:<そのうちACKするけど、まあいいか>
アプリケーション:<無反応>
アプリケーション:<ACKを待っているんだけど、ネットワークが混雑しているのかな>
HAProxy:もう待ち疲れた。はい、ACKだよ。
アプリケーション:やった! じゃあ2つ目のパケットを送るよ。
HAProxy:よかった。これで終了だ。

送受信側が意図的に待機している時間があるが、そこで余分な200msが発生している。
これが、リアルタイム性を要求されている場合わりと致命的な遅延となってしまうのでNagleアルゴリズムを無効化するかTCP_CORKで遅延時間を制御する場合がある

おわりに

楽しかった。

Unicode9.0 以降の複雑な符号化における letter-spacing トラップ

はじめに

どうも、最近レンタルしたレディプレイヤー1が面白すぎて購入を検討しているけんつです。

最近、Webにおいて圧倒的に面倒な絵文字対応について色々と考えているところなのですが
もろもろ対応していると不可解な現象とその原因がわかったので少しだけまとめます。

問題

Unicode9.0以降で絵文字で肌色が選択できるわけなのですが、あれをWeb上で表示させるときに何故か
以下の様に絵文字が 本体+スキントーンの形式に分離してしまう問題が発生していた。

しかもスキントーンは色が四角くなって出てきてしまい、初見ではこれが一体何なのかわからなかった。

f:id:RabbitFoot141:20181112203116p:plain

実際はスキントーン以外でも、国旗や複数人がひとつの絵文字に存在するような場合には同様に分離してしまう箇所がいくつかあった。

そして、なぜか場所によっては正常に表示されている部分もあった。

原因

肌色や複数のコードポイントにまたがって表現される絵文字は以下のような形式を取っている

U+1F466 U+1F3FE

このように結合文字列としてひとつの絵文字を表現しているのだ。

そのため、 letter-spacing が使用されていると本来ひとつの絵文字として表現されているものが
U+1F466 と U+1F3FE に分離してしまうといったものだった。

実証

f:id:RabbitFoot141:20181112204047p:plain

このように、letter-spacingを利用しているかどうかで挙動が異なる



以下のサイトで実際にどういう区切りになっているのかなどがよくわかる。
異体字セレクタセレクタ (α v0.3)

終わりに

知らんがな。