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

僕と MySQL と時々 MariaDB

MySQLで大量のテストデータを用意したい

はじめに

どうも、最近みたハクソーリッジという映画が最高に琴線に響いて何度も見ているけんつです

最近、データベース周りの勉強をしてみようと思ったのですが何かが足りないことに気が付きました。
そう大量のテストデータです。

なので今回は作ります。

環境構築

まずは環境を構築する。
基本的にはMySQLの話になるけども、インフラの勉強をするとき用の環境を後に整える予定なのでdocker-composeを使う。

version: '3'
services:
    mysql:
        image: mysql:5.7
        environment:
            - MYSQL_ROOT_PASSWORD=root
            - MYSQL_USER=sample
            - MYSQL_PASSWORD=sample
            - MYSQL_DATABASE=sample
        volumes:
            - ./docker/mysql:/var/lib/mysql
        ports:
            - "3306:3306"

こんな感じでハイパー雑にymlを用意する。

$ docker-compose up -d
$ mysql -h 127.0.0.1 -u sample -D sample -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.20 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [sample]> 

これでログインできた。
次にinit.sqlという名前でマイグレーションを用意する

CREATE TABLE tweets (
    uid int default 0,
    user varchar(255) not null,
    body varchar(255) not null,
    liked int default 0,
    retweet int default 0,
    include_reply boolean default false,
    created_at datetime default current_timestamp
);

ツイートに見立てて簡略的に作成してみた。

ここまでが環境構築

大量のデータをどうにかして用意する

ここではPHPを使ってスクリプトを組んでしまう。

じゃあ早速用意する

<?php
declare(strict_types=1);

//user name list
$names = ['Jack', 'John', 'Smith', 'Noah', 'Harry', 
    'Sophia', 'Emma', 'Olivia', 'Ava', 'Isabella'];

//generate random body
function generateRandBody() : string {
    $result = '';
    $limit = rand(1, 100);
    for($i = 0; $i < $limit; $i++) {
        $result .= chr(mt_rand(65, 90));
    }
    return $result;
}

$limits = 10;
$count = 0;
for (; $count < $limits; $count+=1) {
     $user = $names[$count%10];
     $body = generateRandBody();
     $liked = rand(0, 1000000000);
     $retweet = rand(0, 1000000000);
     $include_reply = ($count%2 == 0) ? 0 : 1;

     $sql = "INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('".$user."','".$body."',".$liked.",".$retweet.",".$include_reply.");";
     echo $sql . "\n";
        
}

こんなスクリプトを組んでいて思ったのだが、当初はこのスクリプトから挿入させようと思ったがまともに1000万件とか挿入するとめちゃくちゃ遅い。
というわけで、↑はもうすでに変更したものだが最初の10件だけ登録してそれをひたすら単純結合させて増加させてしまおうという作戦に出た。

init.sql

CREATE TABLE tweets (
    uid int primary key auto_increment,
    user varchar(255) not null,
    body varchar(255) not null,
    liked int default 0,
    retweet int default 0,
    include_reply boolean default false,
    created_at datetime default current_timestamp
);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Jack','CSHMFFHXYIAY',101262607,788244618,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('John','BNYPLLSXIFXZQWZSYISTIVUDXICZZJDDTTBHUO',455425608,660218612,1);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Smith','ZVMAHSWKBYYVNDJYVCIINSTYUELEUNVDJKETJGZIAITPJJSCEPLFRTDJFDVMYKVG',155967009,720719955,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Noah','KIDUVZVHTQEIEHJAWRFOTXMERFASBVFNPHSSTSEMAUDPVRGHCSOJFMQHRLEFGAYPOQPKYBKBHEOXOFUERGOK',717252756,32221742,1);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Harry','NEZXVIAVARL',361169251,640449508,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Sophia','NBPOFOVUXPFYWGTLKIYFUUOPUGJMVWKENTEKJDKAKJLDHMFEHSYSXOLWCKLWJ',587239726,530171748,1);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Emma','IIHSNNHUQIPPXLNHEKLSLN',334524943,69008302,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Olivia','BHLZISBQELBLJVFZBTHBJUKAHILWJXKRHLAPYBMHXBPTSPFGVFGGLXTAEKUHMHAFHYAWQKYZ',779212237,102552681,1);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Ava','BUPFCEDTZEQQEBUFEUVDZLWKTNWRKZBLTWLXIWGXWNYEYNEHHRDLYWFAMFMEZQXCZXVSAAJWRTNFXRHYUKWPWAQCXM',40163778,286463918,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Isabella','OQOUHLPDOWJNRIKOYXOOYUEPEVQQENEKZFYZJBEKQPO',763927989,187392711,1);
SOURCE increase.sql;

increase.sql

insert into tweets (
    select
    tweets.uid = null,
    tweets.user, 
    tweets.body, 
    tweets.liked, 
    tweets.retweet, 
    tweets.include_reply,
    tweets.created_at
    from tweets, tweets tweets2, tweets tweets3, tweets tweets4, tweets tweets5, tweets tweets6);

このようにあらかじめ用意した10個のレコードを単純結合させることで膨大なデータを用意する

そしてこれらのファイルを実行するとこうなる。

MySQL [sample]> source init.sql;
Query OK, 0 rows affected (0.51 sec)

Query OK, 1 row affected (0.09 sec)

Query OK, 1 row affected (0.06 sec)

Query OK, 1 row affected (0.11 sec)

Query OK, 1 row affected (0.11 sec)

Query OK, 1 row affected (0.06 sec)

Query OK, 1 row affected (0.06 sec)

Query OK, 1 row affected (0.05 sec)

Query OK, 1 row affected (0.08 sec)

Query OK, 1 row affected (0.05 sec)

Query OK, 1 row affected (0.06 sec)

Query OK, 10000000 rows affected (5 min 13.20 sec)
Records: 10000000  Duplicates: 0  Warnings: 0


MySQL [sample]> source datasize.sql
+------------+--------+----------+------+--------+---------+----------+
| table_name | engine | tbl_rows | rlen | all_mb | data_mb | index_mb |
+------------+--------+----------+------+--------+---------+----------+
| tweets     | InnoDB |  9706337 |  100 |    929 |     929 |        0 |
+------------+--------+----------+------+--------+---------+----------+
1 row in set (0.00 sec)


1000万行程ある、900MB以上の容量を持つテーブルが作成できた。
何故か、30万レコード程損失があるとなっているがselect * from では1000万行あることになっている理由は謎これから調べてみる

おわりに

とりあえず、アホみたいにデカイDBは作れたのでこれを元にいろいろやっていく

PHPerがいくGolang入門 A Tour of Go More types: structs, slices, and maps

はじめに

どうも、年末にアドカレを1日分書き忘れていたので急いで書き始めたけんつです。
今回も例によって全くPHPは関係無いですが、A Tour of Go の続きをやっていこうかなと思います。

Pointer

きたよ、ポインタが。こいつが来ると謎の安心感がある。
宣言、アドレスの参照などは大体Cと同じ。

package main;

import "fmt";

func main() {
    var number int = 12345;
    var pointer *int = &number;

    fmt.Println(pointer);
    fmt.Println(*pointer);
}

Structs

構造体、ここまでくるとCと大体変わらなくなってくる。
構造体そのものもそうだし、構造体をポインタを通して操作することができる。

package main;

import "fmt";

type Vertex struct {
    X int
    Y int
}

func main() {
    var v Vertex = Vertex{1, 2};
    var p *Vertex = &v;

    fmt.Println(v);

    p.X = 3;

    fmt.Println(v);
}
$ go run structs.go 
{1 2}
{3 2}

こんな感じで。さらに Name: のようにフィールドの一部だけを列挙することができる。

Arrays

配列ですよ、こいつがあればなんとか競プロで戦えそう。

package main;

import "fmt";

func main() {
    var primes [5]int;
    primes[0] = 2;
    primes[1] = 3;
    primes[2] = 5;
    primes[3] = 7;
    primes[4] = 11;

    fmt.Println(primes);

    numbers := [2]string{"hello", "world"};

   fmt.Println(numbers);
}

型の明示的宣言に未だに違和感があるがなんとか。

Slice

これ、Pythonのスライスと似たようなことをやっている印象あるんだけど無駄に長いので結構大変だった。
スライスは配列は固定長なのにたいして可変長のやつをさす
使い方はPythonのそれと基本的には同じ。

package main;

import "fmt";

func main() {
    primes := [6]int{2, 3, 5, 7, 11, 13};

    var slice []int = primes[1:3];
    fmt.Println(slice);
}

また、スライスは配列の部分列に対する参照を指しているので元となる配列の値を変更するとスライス先もその変更が反映されてしまう。

スライスは次のようにするとその配列を参照するスライスを作ることができる。

slices := []int{2,3,5,7,11,13}

これは右端の配列に対するスライスが生成される

さらに、組み込み関数のひとつであるmake関数を使用することによっても背製することができる。

a := make([]int, 5)

スライスは可変と言ったが、すでにあるスライスに対して要素を追加する場合はappend関数を使う。

Range

for などで range を使用する場合スライスやマップに対してひとつずつ反復処理をする場合につかう

package main;

import "fmt";

func main() {
    var pow = []int{1, 2, 4, 8, 16};

    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v);
    }
}

Map

mapが使えるみたいなんだが、割と難解な気がする
なぜここでmake関数を使っているのだろうか…

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex

func main() {
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m["Bell Labs"])
	fmt.Println(m)
}

function values

関数もまた変数であるため、他の変数のように渡すことができる。

package main;

import "fmt";
import "math";

func pow(f func(float64, float64) float64) float64 {
    return f(3, 4);
}

func main() {
    
    hypot := func(x float64, y float64) float64 {
        return math.Pow(x, y);
    };

    fmt.Println(hypot(2, 2));
    fmt.Println(pow(hypot));
}

個人的に濃かった激動の2018年を振り返ってみる

はじめに

どうも、最近PS4を買ってしまいゲーム三昧な年末を全力で過ごしているけんつです。
今年は、というか主に今年の後半に色々あったのでちょっと振り返ってみようかなと思ったので書きます。雑に。

初めてインターンに参加した

まず、今年の始めに人生で初めてインターンなるものに参加した。
詳しいことは以下の記事にまとめてある。
rabbitfoot141.hatenablog.com


何をやったかって言われるとプロトコル・スタックにDHCPクライアント機能を追加するということをやった。当時、ScalaJavaしかやっていなかった人間がいきなりカーネルモジュール書いたり、パケット書いたりとがっつりレイヤー下げたのでかなり苦労したけどめちゃいい経験になった。(詳細は後ほど) 参加したのが3月の話だったんだけども、年の始めから全開で飛ばしていた気がする。
ここでは本当によい経験が積めたと思っている。

サーバサイド強化期間

インターン期間中に色々あって、昨年からフルリモートで携わっているWebサーバサイドを少し見なおした。具体的には、PHPで書かれたOSSのコードを1から読んでみたり、それを元にテンプレートエンジン作ってみたりと色々やった。インターンを終えて働き方やサーバサイドに取り組む姿勢みたいなものを見なおした。これも結構大きな出来事というかよくやったな当時の自分、というように思う。
記事はこのあたり。自信のサーバサイドはPHPと共にあり。といった感じになっている。

rabbitfoot141.hatenablog.com

rabbitfoot141.hatenablog.com

rabbitfoot141.hatenablog.com

rabbitfoot141.hatenablog.com

研究室に配属された

今年も後半になった頃、研究室に配属された。機械学習とか画像処理をやっている研究室なんだけども、何故か今そこでWebシステムを作っている。しかもオンプレだけで。結構しんどいんだけど、このあたりから運用を考えた技術選定やシステムアーキテクチャに目を向け始めた。
あとは単純にインフラ周りの知見を深めたり、何故かロギング周りをごりごり調べたり、一番大きなものはJavaScriptが少しかける様になった。Redisも楽しい。
エンジニアのエゴで必要以上にごつ盛りにした技術スタックとか運用を無視した構成や最新の技術を使い倒したいから使うとか本当にその後に触る人によってはその人が地獄を見る可能性があるので良くないと思うようになった。このあたりの経験は開発だけでなく、運用や保守、設計などの一段広い視野を持ち始める良いきっかけになっていると思う。

逆求人なるものに参加した

ついに就活ですよ。サポーターズさんのやつに行ってきた。
rabbitfoot141.hatenablog.com

これは驚きの連続だった。詳細は↑を。

2018年は

あんまり意識はしていなかったが今年はカーネルモジュールを書き、パケットを書き、サーバサイドを書き、(ここには書いてないが)インフラをガッツリ触り、システムの設計をしてとバックエンドごつ盛りのような生活を送っていたことにこの時点で気がついた。
序盤はこれまで不足しがちなCS周りの知見を貯めて、その後はサーバサイドを書き、サーバサイドをもっと書くためにインフラを構築し、それらでプロダクトを作るために設計や運用を考える。完璧な流れが今年は出来上がっていた。
作ってきたものが、githubでスター3桁どころか2桁ですら届かないようなものだったがただただ面白くて作っていた(特にカーネルモジュールとしてEchoサーバを作るとか)から全く気が付かなかった。けど、割とフロント以外はフルスタックな感じのとてつもなくいい経験を積めた一年になった。

来年の目標

今抱えている、タスクをひとまず全て片付けたい。
あと、進路が決まりそうなので決まったらそれにむかって更に精進する。
そして卒業する。

あと、これは絶対やりたいなと思っているんだけどフルスタック感全開できたからよく思うんだが自分には「これなら出来る」っていう分野が何一つ無いので自信をもってこれは出来ると言える分野を見つけたい。

こんな感じでやっていくので、来年もどうぞよろしくお願いいたします。
それでは良いお年を。

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社(?、あまり覚えていない)と面談し半日ほどしゃべりっぱなしで声がガラガラになったり
あと、シンプルに疲れます。笑


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


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

後日談

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

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


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


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