KotlinでSpring Bootでhello worldを出してみる

KotlinのSpring Bootでhello worldを出してみました。

前提条件

  • JDKがインストール済み
  • Intellij IDEAもインストール済み

Spring Bootのフレームワークをダウンロード

Spring Initializrのから設定してダウンロード

設定の参考url

https://qiita.com/kawasaki_dev/items/1a188878eb6928880256#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E9%9B%9B%E5%BD%A2%E3%82%92%E4%BD%9C%E6%88%90

GradleをインストールしてSpring Bootを起動してみる

Intellij IDEAからダウンロードしたSpring BootのフレームワークをOpenする。

そうすると右下にGradleをインストールしますかのようなポップアップが現れるので、そこからインストールをしてもらう。

GradleがインストールされるとProjectパネルに.gradelとbuildのディレクトリが作成され、main関数の横にrunボタンが表示されるのでrunボタンから起動してWEBアプリケーションを実行します。

Spring BootはWebサーバーが内包されている

PHPですとnginxやapacheなどのWebサーバを用意する必要がありますが、Spring Bootのフレームワーク自体にWebサーバが内包されているのでmain関数の横にrunボタンを押すだけでwebアプリケーションを起動できます、便利ですね。
ちなみに、AWSのLambdaのようなものをサーバレスといいますが、このような「WEBサーバ」の用意が不要なこともサーバレスというようです。

hello worldを表示するコード

https://github.com/unamu1229/spring-boot-demo/pull/2/files
のファイルを追加します。
再度main関数をrunすると http://localhost:8080/ にhello worldと表示されました。

参考url

Spring Initializr

https://qiita.com/kawasaki_dev/items/1a188878eb6928880256#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E9%9B%9B%E5%BD%A2%E3%82%92%E4%BD%9C%E6%88%90

サーバレス

https://qiita.com/kannkyo/items/c3d25553fc505150bfdf#31-%E3%82%B5%E3%83%BC%E3%83%90%E3%83%AC%E3%82%B9%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3

Controller

https://spring.pleiades.io/guides/tutorials/spring-boot-kotlin/

https://blog.qbist.co.jp/?p=2654

インターフェイス名はクライアント側に由来する名前にする

インターフェイスの名前を命名する時に、サーバー側の名前ではなくクライアント側の名前をつける必要があるそうです。
このソースは、アジャイルソフトウェア開発の奥義(ロバート・C・マーチン)9.3 鍵は「抽象」にありの項目(P130)に書かれていました。
理由は、抽象クラスはそれを実際に実装するクラスとの関係よりも、それを利用するクラスとの関係の方がずっと密接だからだそうです。

Laravelのapp()->make()はアンチパターンなの?

Laravelのapp()->make()はアンチパターンなのか調べてみました。

結論

フレームワークに依存すると問題のあるドメイン層以外ではapp()->make()を利用してもいい。

調査内容

アプリケーションサービスのメソッド内でapp()->make()を利用していたら、
疎結合を意識してコンストラクタインジェクションを利用するようにコードレビューをもらったことがあったのですが、app()->make()は疎結合だし、コンストラクタからタイプヒンティングでプロパティに持たせるほうがインスタンスの作成時に必ず依存することで循環参照になりやすく結合度は高そうと思ったのでいろいろ調べて見ました。

パターンの種類でいうと
app()->make()はServiceLocaorで、
コンストラクタインジェクションはDIになります。

双方のバターンとも疎結合を基本としている。
という記事や、
そもそもDIは依存性の解決であって、依存が無くなるわけではない。
という記事があったりして正反対な意見があるようですが、ServiceLocaorをDIにすることで、サービスロケータ自体には依存しないようになるようです。

そのため、ServiceLocaorがアンチパターンのような記事をいくつか見かけたのですが、マーティンファウラー先生によるとそれは違うようです。
先生によるとDIよりService Locatorが利用できるのであれば、それを利用するのはもっともだとおっしゃっています。

1
Service Locator と Dependency Injection とのどちらを採用するかの判断は、 ロケータへの依存性が問題になるかどうかにかかっている。

とありましたので、Laravelのフレームワークを利用する上で、フレームワークに依存すると問題のあるドメイン層以外では Service Locatorを採用したほうがよさそうです。

また、同様の意見が「上田勲. プリンシプル オブ プログラミング 3年目までに身につけたい 一生役立つ101の原理原則」にも書かれていました。

1
2
結合先モジュールの質にも着目しましょう。密な結合自体は問題ですが、本質的には、不安定な要素と密に結合するのが問題になります。安定的なライブラリに依存するのは特に問題ではありません。盲目的にデータ結合を目指すというより、相手によって付き合いの深さを判断しましょう。
`

「データ結合」とは、この本の中でもっとも疎結合であるという形です。
フレームワークとして提供されるサービスロケータの質は安定的しているといえるので、盲目的に疎結合を目指してDIを利用するということは良くないようです。

参考url

マーティンファウラー

https://kakutani.com/trans/fowler/injection.html#ServiceLocatorVsDependencyInjection

マーティンファウラーとは反対の意見

http://blog.a-way-out.net/blog/2015/08/31/your-dependency-injection-is-wrong-as-I-expected/

日毎のlogrotateをテストで動かしたい

日毎のlogrotateをテストで動かしたい

AmazonLinuxで日毎の設定のlogrotateの動作確認をしたいと思い。
対象のファイルの日付をtouchで昨日にして、
/usr/sbin/logrotate /etc/logrotate.conf を実行してもログファイルが作成されませんでした。

調べてみると
/var/lib/logrotate/logrotate.status
というファイルに前回のログローテートを行った時刻を記載しているとのこと。

/var/lib/logrotate/logrotate.status に先程logrotateコマンドをした日時が記録されていたので、
その日時を昨日に変更して、再度 /usr/sbin/logrotate /etc/logrotate.conf を実行すると

無事ログファイルがローテートされました。

tips

調べている際に、logrotateは/etc/cron.dailyで実行されていて。
cron.dailyの実行は/etc/anacrontabで設定されているということを知りました。

参考サイト

https://www.khstasaba.com/?p=958
https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/deployment_guide/ch-automating_system_tasks#s2-configuring-anacron-jobs

systemd Failed to execute command: Permission denied

CentOS8にてsystemdのtest.serviceを作成してみてsystemd start testを行ってみると実行に失敗、
/var/log/messagesに下記のエラーが表示されていました。

1
2
3
Mar 20 13:21:45 unamu systemd[1]: Started testscript.
Mar 20 13:21:45 unamu systemd[83619]: test.service: Failed to execute command: Permission denied
Mar 20 13:21:45 unamu systemd[83619]: test.service: Failed at step EXEC spawning /tmp/test: Permission denied

Permission deniedの表示からファイルのパーミッションを確認しましたが、問題無し。
SELinuxが邪魔をしていたようです。
有効になっているか確認

1
2
$ getenforce
Enforcing

Permissiveモードにします。

1
$ setenforce 0

その後、systemd start testで無事実行されました。

参考url

https://fujiyasu.hatenablog.com/entry/2016/08/05/094804

ファイアウォールでpingの疎通確認を拒否する

CentOS8のfirewall-cmdでpingの疎通確認を拒否を試してみました。
pingはicmpのプロトコルです。

firewallの設定内容を確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost unamu]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: enp0s3
sources:
services: cockpit dhcpv6-client http ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

icmp-block-inversionがyesの時にicmp-blocksに記載されたICMP Typeを受け入れ、
noの時はicmp-blocksに記載されたICMP Typeを拒否します。
icmp-blocksに何も設定されていないので、icmp-block-inversionをyesにすることですべてのICMP Typeを拒否することで
pingを拒否できそうです。

icmp-block-inversionをyesにする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost unamu]# firewall-cmd --add-icmp-block-inversion
success
[root@localhost unamu]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: yes
interfaces: enp0s3
sources:
services: cockpit dhcpv6-client http ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

icmp-block-inversionが yes になったので、pingを実行して拒否されるか確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~/D/g/D/laradock ❯❯❯ ping 192.168.11.17                                            (git)-[master]
PING 192.168.11.17 (192.168.11.17): 56 data bytes
92 bytes from 192.168.11.17: Communication prohibited by filter
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
4 5 00 5400 59d4 0 0000 40 01 896f 192.168.11.4 192.168.11.17

Request timeout for icmp_seq 0
92 bytes from 192.168.11.17: Communication prohibited by filter
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
4 5 00 5400 9edf 0 0000 40 01 4464 192.168.11.4 192.168.11.17

Request timeout for icmp_seq 1
92 bytes from 192.168.11.17: Communication prohibited by filter
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
4 5 00 5400 40eb 0 0000 40 01 a258 192.168.11.4 192.168.11.17

^C
--- 192.168.11.17 ping statistics ---
3 packets transmitted, 0 packets received, 100.0% packet loss

100.0% packet loss となっているので拒否できているようです。

icmp-block-inversionをnoに戻しておきます。

1
2
[root@localhost unamu]# firewall-cmd --remove-icmp-block-inversion
success

応答自体を返さない場合はtargetをDROPにすることで可能です、
targetの設定を反映するにはreloadが必要なようです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@localhost unamu]# firewall-cmd --set-target=DROP --permanent
success
[root@localhost unamu]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: enp0s3
sources:
services: cockpit dhcpv6-client http ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
[root@localhost unamu]# firewall-cmd --reload
success
[root@localhost unamu]# firewall-cmd --list-all
public (active)
target: DROP
icmp-block-inversion: no
interfaces: enp0s3
sources:
services: cockpit dhcpv6-client http ssh
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

pingで応答が無いか確認

1
2
3
4
5
6
7
8
9
~/D/g/D/laradock ❯❯❯ ping 192.168.11.17                                            (git)-[master]
PING 192.168.11.17 (192.168.11.17): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
Request timeout for icmp_seq 3
^C
--- 192.168.11.17 ping statistics ---
5 packets transmitted, 0 packets received, 100.0% packet loss

AWSのサービスにpingを実行した時と同じ反応です、
これが応答が無い場合のリアクションなんですね。

元に戻しておきます。

1
2
3
4
[root@localhost unamu]# firewall-cmd --set-target=default --permanent
success
[root@localhost unamu]# firewall-cmd --reload
success

参考サイト

https://milestone-of-se.nesuke.com/sv-basic/linux-basic/firewall-cmd/
https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/security_guide/sec-managing_icmp_requests
https://milestone-of-se.nesuke.com/sv-basic/linux-basic/drop-ping-on-linux/

Goの使い始めにハマったこと

GOPATH 関係

コードポイント 関係

ElasticSearchでqueryの結果が下記の\u6771\u4eac0ように日本語がUTF16のコードポイントとして表示されていた。
本当はprefの項目は東京0と表示されて欲しかった。

1
2
3
4
5
body, _ := ioutil.ReadAll(resp.Body)
mt.Println(string(body))

# コンソール出力
{"took":27,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":1.0,"hits":[{"_index":"search_job","_type":"_doc","_id":"0","_score":1.0,"_source":{"pref":"\u6771\u4eac0","employment":"full-time","line":{"name":"\u5c71\u624b\u7dda","station":["\u6771\u4eac"]}}}]}}

日本語がUTF16のコードポイントとして表示されており、
tranceformパッケージでurf16のバイトのDecodeを試したが文字化けして失敗。
(失敗例)

1
2
3
4
5
6
7
8
9
body, _ := ioutil.ReadAll(resp.Body)
hoge, _, _ := transform.Bytes(
unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder(),
body,
)
fmt.Println(string(hoge))

# コンソール出力
≻潴歯㨢ⰵ琢浩摥潟瑵㨢慦獬ⱥ弢桳牡獤㨢≻潴慴≬ㄺ∬畳捣獥晳汵㨢ⰱ猢敲慬楴湯㨢攢≱ⱽ洢硡獟潣敲㨢⸱ⰰ栢瑩≳嬺≻楟摮硥㨢猢慥捲彨潪≢∬瑟≦∺畜㜶ㄷ畜攴捡∰∬浥汰祯敭瑮㨢昢汵⵬楴敭Ⱒ氢湩≥笺渢浡≥∺畜挵ㄷ畜㈶戴畜搷慤Ⱒ猢慴楴湯㨢≛畜㜶ㄷ畜攴捡崢絽嵽絽

structにqueryの結果を入れて表示すると、期待通り東京0と表示されました。
Unmarshalの中でdecodeがされているようです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
type GetResponse struct {
Took int `json:"took"`
TimedOut bool `json:"timed_out"`
Shards struct {
Total int `json:"total"`
Successful int `json:"successful"`
Skipped int `json:"skipped"`
Failed int `json:"failed"`
} `json:"_shards"`
Hits struct {
Total struct {
Value int `json:"value"`
Relation string `json:"relation"`
} `json:"total"`
MaxScore float64 `json:"max_score"`
Hits []struct {
Index string `json:"_index"`
Type string `json:"_type"`
ID string `json:"_id"`
Score float64 `json:"_score"`
Source struct {
Pref string `json:"pref"`
Employment string `json:"employment"`
Line struct {
Name string `json:"name"`
Station []string `json:"station"`
} `json:"line"`
} `json:"_source"`
} `json:"hits"`
} `json:"hits"`
}

func main() {
body, _ := ioutil.ReadAll(resp.Body)
var getResponse GetResponse
unmarshalErr := json.Unmarshal(body, &getResponse)
if unmarshalErr != nil {
fmt.Println(unmarshalErr)
}

for _, hit := range getResponse.Hits.Hits {
fmt.Println("pref", hit.Source.Pref)
}
}

# コンソール出力
pref 東京0

json全体ではなく、コードポイントの箇所のみであれば、Unquoteで文字に変換できました。

1
2
3
4
5
t := "\\u6771\\u4eac0"
converted, _ := strconv.Unquote(`"` + t + `"`)
fmt.Println(converted)
# コンソール出力
東京0

Elasticsearch 使い方

実行環境

  • Elasticsearch 7.10.1
  • Kibana 7.10.1

    検索

完全一致検索

データ項目のタイプがキーワードとして登録されている必要があります。
検索対象のtypekeywordで、querytermを指定して行う必要があります。

request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#対象index作成 都道府県(pref)のtypeを完全一致の対象にする為にkeywordで登録する
PUT search_job
{
"mappings" : {
"properties": {
"pref": {
"type": "keyword"
}
}
}
}

#type確認
GET search_job/_mapping

response

1
2
3
4
5
6
7
8
9
10
11
{
"search_job" : {
"mappings" : {
"properties" : {
"pref" : {
"type" : "keyword"
}
}
}
}
}

request

1
2
3
4
5
6
7
データ登録
POST /search_job/_doc
{
"pref" : "東京"
}
# データ確認
GET /search_job/_search

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job",
"_type" : "_doc",
"_id" : "jS17e3YB0KvFjllCp3-y",
"_score" : 1.0,
"_source" : {
"pref" : "東京"
}
}
]
}
}

request

1
2
3
4
5
6
7
8
9
# queryの種類をtermにすることで完全一致検索
GET /search_job/_search
{
"query" : {
"term": {
"pref":"東京"
}
}
}

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "search_job",
"_type" : "_doc",
"_id" : "jS17e3YB0KvFjllCp3-y",
"_score" : 0.2876821,
"_source" : {
"pref" : "東京"
}
}
]
}
}

request

1
2
3
4
5
6
7
8
9
# queryの種類をtermにすることで完全一致検索
GET /search_job/_search
{
"query" : {
"term": {
"pref":"東"
}
}
}

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 該当するものは無い
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}

indexを定義せずにデータ登録した場合

request

1
2
3
4
POST /search_job2/_doc
{
"pref" : "東京"
}

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# typeでtextで登録されて、fieldsにtype:keywordでも登録されます
{
"search_job2" : {
"mappings" : {
"properties" : {
"pref" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}

request

1
2
3
4
5
6
7
8
9
# termの対象をpref.keywordとすることで、fieldsのtype:keywordを対象とすることができます
GET /search_job2/_search
{
"query" : {
"term" : {
"pref.keyword": "東京"
}
}
}

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "search_job2",
"_type" : "_doc",
"_id" : "ki2Ne3YB0KvFjllCJX_C",
"_score" : 0.2876821,
"_source" : {
"pref" : "東京"
}
}
]
}
}

複数検索条件の完全一致

queryのboolmustで複数termを指定する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#request
PUT search_job3
{
"mappings" : {
"properties": {
"pref": {
"type": "keyword"
},
"employment": {
"type": "keyword"
}
}
}
}
#response
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "search_job3"
}
#request
GET search_job3/_mapping
#response
{
"search_job3" : {
"mappings" : {
"properties" : {
"employment" : {
"type" : "keyword"
},
"pref" : {
"type" : "keyword"
}
}
}
}
}
#request
POST /search_job3/_doc
{
"pref" : "東京",
"employment" : "full-time"
}
#response
{
"_index" : "search_job3",
"_type" : "_doc",
"_id" : "lS1OfXYB0KvFjllCZ3-D",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
#request
GET /search_job3/_search
#response
{
"took" : 484,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job3",
"_type" : "_doc",
"_id" : "lS1OfXYB0KvFjllCZ3-D",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time"
}
}
]
}
}

#request
#複数検索条件の完全一致
GET /search_job3/_search
{
"query" : {
"bool": {
"must" : [
{
"term": {
"pref": "東京"
}
},
{
"term": {
"employment": "full-time"
}
}
]
}
}
}
#response
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "search_job3",
"_type" : "_doc",
"_id" : "lS1OfXYB0KvFjllCZ3-D",
"_score" : 0.5753642,
"_source" : {
"pref" : "東京",
"employment" : "full-time"
}
}
]
}
}

ネストした検索対象への完全一致検索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#  路線が駅を持つようなネストしたデータを作成します
# request
PUT search_job4
{
"mappings" : {
"properties": {
"pref": {
"type": "keyword"
},
"employment": {
"type": "keyword"
},
"line": {
"properties" : {
"name": {
"type": "keyword"
},
"stations" : {
"properties" : {
"name" : {
"type" : "keyword"
}
}
}
}
}
}
}
}

POST /search_job4/_doc
{
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}

GET /search_job4/_search

# response
# 路線が駅を持つようなネストしたデータが登録できました
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "li1_fXYB0KvFjllCmn_A",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
}
]
}
}

# resquest
# ネストされている駅名に対して完全一致検索をします
GET /search_job4/_search
{
"query" : {
"term": {
"line.stations.name": "渋谷"
}
}
}

ネストした検索対象にネスト構造の経路の関係(Nested datatype)をもたせる

ネストした経路の関係(Nested datatype)を持たせたい場合にtypeにnested指定してmappingをします。

先程のmappingだと、何線の何駅かというのは検索できません。
下記のようなデータを追加した場合。
山手線の渋谷駅というデータを絞りこめません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# request
POST /search_job4/_doc
{
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}

GET /search_job4/_search

# response
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "li1_fXYB0KvFjllCmn_A",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
},
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "mC2sfXYB0KvFjllCHn8N",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}
}
]
}
}

# request
# 山手線の渋谷駅で検索
GET /search_job4/_search
{
"query" : {
"bool": {
"must" : [
{
"term": {
"line.name": "山手線"
}
},
{
"term": {
"line.stations.name": "渋谷"
}
}
]
}
}
}

# response
# 山手線の渋谷駅でないものも含まれてしまう。
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.47851416,
"hits" : [
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "li1_fXYB0KvFjllCmn_A",
"_score" : 0.47851416,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
},
{
"_index" : "search_job4",
"_type" : "_doc",
"_id" : "mC2sfXYB0KvFjllCHn8N",
"_score" : 0.47851416,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}
}
]
}
}

これは、追加したデータがElasticsearchの内部ではネスト構造を保たず、項目ごとにグルーピングされたような構造(Object datatype)で解釈される為になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"line" : {
"name" : [
"東横線",
"山手線"
],
"stations" : {
"name" : [
"渋谷",
"中目黒",
"横浜",
"東京",
"神田",
"浅草"
]
}
}
}

路線のtypenestedにしてネスト構造を保ったままmappingし、ネスト構造の経路を指定して検索してみる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# request
PUT search_job5
{
"mappings" : {
"properties": {
"pref": {
"type": "keyword"
},
"employment": {
"type": "keyword"
},
"line": {
"type" : "nested",
"properties" : {
"name": {
"type": "keyword"
},
"stations" : {
"properties" : {
"name" : {
"type" : "keyword"
}
}
}
}
}
}
}
}

GET search_job5/_mapping
# response
# line(路線)のtypeがnestedになっていることがわかる
{
"search_job5" : {
"mappings" : {
"properties" : {
"employment" : {
"type" : "keyword"
},
"line" : {
"type" : "nested",
"properties" : {
"name" : {
"type" : "keyword"
},
"stations" : {
"properties" : {
"name" : {
"type" : "keyword"
}
}
}
}
},
"pref" : {
"type" : "keyword"
}
}
}
}
}

# request
POST /search_job5/_doc
{
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}

POST /search_job5/_doc
{
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}

GET /search_job5/_search
# response
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "search_job5",
"_type" : "_doc",
"_id" : "mS3wfnYB0KvFjllCcn9x",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
},
{
"_index" : "search_job5",
"_type" : "_doc",
"_id" : "mi3wfnYB0KvFjllCk3_f",
"_score" : 1.0,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : [
{
"name" : "東横線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "中目黒"
},
{
"name" : "横浜"
}
]
},
{
"name" : "山手線",
"stations" : [
{
"name" : "東京"
},
{
"name" : "神田"
},
{
"name" : "浅草"
}
]
}
]
}
}
]
}
}

# request
# nestedのpathにlineを指定し、line(路線)のネスト構造の経路内で山手線の渋谷を検索する
GET /search_job5/_search
{
"query": {
"nested": {
"path": "line",
"query" : {
"bool": {
"must" : [
{
"term": {
"line.name": "山手線"
}
},
{
"term": {
"line.stations.name": "渋谷"
}
}
]
}
}
}
}
}

# response
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.1162586,
"hits" : [
{
"_index" : "search_job5",
"_type" : "_doc",
"_id" : "mS3wfnYB0KvFjllCcn9x",
"_score" : 1.1162586,
"_source" : {
"pref" : "東京",
"employment" : "full-time",
"line" : {
"name" : "山手線",
"stations" : [
{
"name" : "渋谷"
},
{
"name" : "恵比寿"
},
{
"name" : "品川"
}
]
}
}
}
]
}
}

参考url

core dumpについて

php-fpmのログで下記のようなSIGSEGVのエラーがでており、原因を調査する場合にcore dumpという方法があるようなので試してみました。

1
WARNING: [pool www] child 17248 exited on signal 11 (SIGSEGV)

検証環境

CentOS8

core dumpとは

システムが異常終了した場合に、その時点のメモリの内容を記録したcoreファイルを吐き出すことを指すようです。

coreファイルの出力先を確認

1
2
$ cat /proc/sys/kernel/core_pattern
core

coreとなっている場合、
プロセスの作業ディレクトリに作成されます。
プロセスの作業ディレクトリは、
ls -l /proc/{プロセスID}/cwd のリンク先になります。
プロセスが終了すると /proc/{プロセスID} のディレクトリは無くなるので、
確認する場合はプロセスが生きている間に確認する必要があります。
プロセスを生きている間に確認することが難しい場合は、
/proc/sys/kernel/core_patternに適当なディレクトリを作成することでcoreの出力先を指定できます。
osを再起動すると元の設定にもどります。

1
echo '/tmp/core-%e-%p' > /proc/sys/kernel/core_pattern

%eは、プログラム名。%pはプロセスIDとして置き換わります。

CentOS8の場合

1
2
$ cat /proc/sys/kernel/core_pattern
|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h %e

となっており、パイブでわたされた、/usr/lib/systemd/systemd-coredumpが
/var/lib/systemd/coredumpのディレクトリ直下にコアダンプが出力されるようになっていました。
また、coredumpctlでcoreの情報を見たりできます。

最近作成されたcoreの一覧を表示。

1
2
3
$ coredumpctl list
TIME PID UID GID SIG COREFILE EXE
Sun 2020-12-06 22:29:22 JST 3252 1000 1000 11 present /usr/bin/bash

coredumpctl info に pidを指定して情報を見る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ coredumpctl info 3252
PID: 3252 (segv.sh)
UID: 1000 (unamu)
GID: 1000 (unamu)
Signal: 11 (SEGV)
Timestamp: Sun 2020-12-06 22:29:21 JST (6min ago)
Command Line: /bin/bash ./segv.sh
Executable: /usr/bin/bash
Control Group: /user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service
Unit: user@1000.service
User Unit: gnome-terminal-server.service
Slice: user-1000.slice
Owner UID: 1000 (unamu)
Boot ID: 35a2b597dc5a4a6ebe37fd3a7e97bd73
Machine ID: c4f822f021d5433ab143f054bca5b672
Hostname: localhost.localdomain
Storage: /var/lib/systemd/coredump/core.segv\x2esh.1000.35a2b597dc5a4a6ebe37fd3a7e97bd73.3252.1607261361000000.lz4
Message: Process 3252 (segv.sh) of user 1000 dumped core.

Stack trace of thread 3252:
#0 0x00007fe849946d79 _int_malloc (libc.so.6)
#1 0x00007fe84994850e malloc (libc.so.6)
#2 0x00005625a34934c2 xmalloc (bash)

coreファイルのファイルサイズの変更

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat /proc/self/limits
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size unlimited unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 3140 3140 processes
Max open files 1024 262144 files
Max locked memory 65536 65536 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 3140 3140 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us

の Max core file size の項目がCoreファイルサイズの設定になります。
softlimitが0だとcoreファイルが作成されません。

unlimitedに設定すると上限なしで作成してくれます。

1
$ ulimit -c unlimited

OSを再起動すると元の設定値に戻ります。

SIGSEGVエラーを出して、coreファイルが作成されている確認する

下記のシェルスクリプトを実行して、SIGSEGVエラーを出す。

segv.sh

1
2
3
4
5
#!/bin/bash
function func {
func
}
func
1
2
3
4
# ./segv.sh 
Segmentation fault (コアダンプ)
# ls /tmp
core-segv.sh-41856

(コアダンプ)と表示されて出力を指定した先にcoreファイルが作成されています。

coreファイルの中身を確認

gdb 実行プログラミング coreファイル でgdbでデバックできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# gdb /bin/bash /tmp/core-segv.sh-41856

GNU gdb (GDB) Red Hat Enterprise Linux 8.2-11.el8
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /bin/bash...Reading symbols from .gnu_debugdata for /usr/bin/bash...(no debugging symbols found)...done.
(no debugging symbols found)...done.

warning: core file may not match specified executable file.
[New LWP 41856]

warning: Loadable section ".note.gnu.property" outside of ELF segments
Core was generated by `/bin/bash ./segv.sh'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000055b592ffb6db in expand_word_list_internal ()
Missing separate debuginfos, use: yum debuginfo-install bash-4.4.19-10.el8.x86_64
(gdb)

gdbの対話モードになったら bt or backtrackでコアダンプされる前の状態をさかのぼって原因を調査できます。

1
2
3
4
5
6
7
8
9
10
11
(gdb) bt
#0 0x000055b592ffb6db in expand_word_list_internal ()
#1 0x000055b592fd2fa7 in execute_simple_command ()
#2 0x000055b592fd51a6 in execute_command_internal ()
#3 0x000055b592fd4ab5 in execute_command_internal ()
#4 0x000055b592fd7a79 in execute_function.isra ()
#5 0x000055b592fd4124 in execute_simple_command ()
#6 0x000055b592fd51a6 in execute_command_internal ()
#7 0x000055b592fd4ab5 in execute_command_internal ()
#8 0x000055b592fd7a79 in execute_function.isra ()
#9 0x000055b592fd4124 in execute_simple_command ()

参考url

https://www2.filewo.net/wordpress/2019/12/16/centos%E3%81%A7%E3%81%AF%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%A7%E3%82%B3%E3%82%A2%E3%83%80%E3%83%B3%E3%83%97%E3%81%8C%E5%90%90%E3%81%8B%E3%82%8C%E3%81%AA%E3%81%84%E3%81%AE%E3%81%A7/

https://rabbitfoot141.hatenablog.com/entry/2016/11/14/153101

https://rheb.hatenablog.com/entry/systemd-coredump

https://qiita.com/suzutsuki0220/items/aa84d7e2e8f37e867f3d

https://qiita.com/rarul/items/d33b664c8414f065e65e

php-fpm

https://www.bit-hive.com/articles/20190206

AWS EKS にnginxのPodをデプロイしてブラウザからアクセスする

Docker/Kubernetes実践コンテナ開発入門の本を読んでいて5.10.1のIngressを通じたアクセスをAmazon EKSで行う場合はどのようになるのか調べました。

利用しているdocker image

gihyodocker/echo : Hollo Docker!!と表示するwebサーバー
gihyodocker/nginx-proxy : echoに対してリバースプロキシする

手順

ReplicaSet

同じ仕様のPodを複数作成する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: echo-summer
labels:
app: echo
release: summer
spec:
replicas: 2
selector:
matchLabels:
app: echo
release: summer
template:
metadata:
labels:
app: echo
release: summer
spec:
containers:
- name: nginx
image: gihyodocker/nginx:latest
env:
- name: BACKEND_HOST
value: localhost:8080
ports:
- containerPort: 80
- name: echo
image: gihyodocker/echo:latest
ports:
- containerPort: 8080

Service

Podの集合に対して、アクセス経路を作成する。typeをNodePortにすることでグローバルなポートを開ける。OSI参照モデルのレイヤー4までしか扱えない。

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: "echo"
spec:
type: NodePort
selector:
app: echo
release: summer
ports:
- port: 80
targetPort: 80
protocol: TCP

Ingress

ServiceのKudernetesクラスタ外への公開。
VirtualHostやパスベースでのHTTP、HTTPSベースでのルーティングが可能。
Ingressが作成されると、EKSのALB Ingress ControllerがALBと必要なAWSサポートリソースを作成してくれる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: "echo"
annotations:
kubernetes.io/ingress.class: alb # Ingress が ALB Ingress Controllerを使用することを設定
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip # Serviceでアクセス経路が作成されたPodにトラフィックをルーティングすることを設定
labels:
app: echo
spec:
rules:
- http:
paths:
- path: /*
backend:
serviceName: "echo"
servicePort: 80