Hike News
Hike News

วิธีชนะมิตรและจูงใจคน - How to Win Friends and Influence People

วิธีชนะมิตรและจูงใจคน - How to Win Friends and Influence People

วิธีชนะมิตรและจูงใจคน - How to Win Friends and Influence People

สำหรับหนังสือเล่มนี้ผมไปเจอจากใน Internet ว่าเป็นหนังสือที่คนควรอ่านให้ได้ก็เลยลองหาซื้อดู ปรากฏว่าหมดจ้า ไม่ว่าจะร้านหนังสือไหนจะเหลือก็แต่ในห้องสมุดสถาบันที่ผมเคยอยู่ (ซึ่งผมเรียนจบแล้วใช้ไม่ได้แล้ว) กับหนังสือมือสองซึ่งก็น่าลองแต่เขาไม่รับรองคุณภาพหนังสือซึ่งน่ากลัวว่าจะได้มาแบบหน้าขาดๆ ก็เลยไม่ได้ซื้อ จนเมื่อปีที่แล้วเขามี Re print หนังสือเล่มนี้ ก็เลยไปป่าวประกาศในหน้า Facebook ว่าหนังสือเล่มนี้น่าอ่านเดี๋ยวปีใหม่จะซื้อมาอ่าน ซึ่งปีใหม่ก็ได้หนังสือเล่มนี้มาจริงๆจากการได้เป็นของขวัญ โอเคเราเล่าที่มาของการได้หนังสือเล่มนี้มาเยอะแล้ว (ไม่ได้เกี่ยวกับเนื้อหาในหนังสือ)

การทำยังไงให้คนที่อยากจะสื่อสารสบายใจและประทับใจ

ในหนังสือเล่มนี้พูดถึงการสื่อสารระหว่างมนุษย์ยังไงให้มีประสิทธิภาพ ซึ่งมันเป็นเรื่องเล่าผ่านประสบการณ์ของ Dale Carnegie ซึ่งผมก็ไม่รู้หรอกว่าเขาคือใคร (แต่พอมารู้ที่หลังเขาก็ยิ่งใหญ่และได้ความเคารพนับถือในความสามารถในการสื่อสารของเขา) ซึ่งในหนังสือจะแบ่งเป็นบทต่างๆ เป็นหัวข้อ หัวข้อ ซึ่งเขาจะยกตัวอย่างกรณีต่างๆ ตามข้อต่างๆให้เราเห็น เช่น ปฏิเสธยังไงให้ผู้ที่ถูกปฏิเสธไม่โกรธ การตำหนิยังไงให้ผู้ถูกตำหนิรับฟังโดยปราศจากอคติ ซึ่งตัวอย่างนั้นเป็นตัวอย่างที่เกิดขึ้นจริง (ถ้าคุณ Dale Carnegie ไม่ได้โกหก) ซึ่งเป็นตัวอย่างที่อ่านแล้วจับต้องได้ แล้วถ้าคิดตามมันเกิดขึ้นได้จริง และผมก็เคยเจอกรณีแบบในหนังสือนี้บ้างเหมือนกัน

เป็นเรื่องที่ควรศึกษา

พอได้อ่านหนังสือเล่มนี้ผมเข้าใจว่าทำไมคนที่เคยอ่านเขาถึงแนะนำให้อ่าน เพราะมันเป็นเรื่องที่ต้องใช้ในชีวิตประจำวัน ขึ้นชื่อว่าคน (หรือมนุษย์แล้วแต่จะเรียก) นั้นย่อมมีอารมณ์ความรู้สึก ซึ่งมันมีทั้งข้อดีและเสีย การสื่อสารกับเขาแล้วจะเกิดผลดีได้ย่อมเกี่ยวกับอารมณ์ เช่น ถ้าจะตำหนิต้องตำหนิแบบไหนถึงให้เขาไม่โกรธและฟังเรา ถ้าเขาโกรธขึ้นมาแน่นอนว่าเขาไม่ฟังสิ่งที่คุณจะตำหนิแน่เขาจะตั้งกำแพงสูงเสียดฟ้าขึ้นมาป้องกันตัวเองพร้อมบอกว่า “ฉันไม่ผิด”

ตัวอย่างที่คุณ Dale Carnegie แกแนะนำคือ บอกว่าเราก็เคยผิดพลาดแบบนี้ ทำแบบนี้มาก่อน แล้วค่อยบอกว่าอีกฝ่ายก็ผิดเหมือนกับที่เราผิด แล้วเราไม่อยากให้เขาผิดแบบเราอีก ซึ่งผมก็เคยโดนสอนด้วยวิธีแบบนี้เหมือนกัน ซึ่งก็คือ หัวหน้าผมตำหนิผมเรื่องการควบคุมอารมณ์เวลาประชุม (เพราะในห้องประชุมมันมีมากมายหลายฝ่าย ฝ่ายที่พูดความจริง ฝ่ายที่แม่งจะไม่ทำห่าอะไรโบ้ยให้ชาวบ้านทำ หรือทำเป็นจำไม่ได้ ซึ่งผมจะมีอารมณ์กับไอสองพวกหลังเสมอแล้วพออารมณ์มาความบรรลัยก็จะมาเยี่ยมเยียนห้องประชุม จากจะหาข้อสรุปมันจะหลายเป็นเวทีมวยลุมพินี) หัวหน้าผมยกตัวอย่างว่าตัวเองก็เคยเป็นแบบนั้น ซึ่งก็บอกว่ามันไม่เป็นผลดี แล้วก็ค่อยบอกว่าผมก็เป็นเหมือนที่หัวหน้าเป็นซึ่งมันไม่ดีก็ควรจะต้องปรับเปลี่ยน ซึ่งหัวหน้าแกก็เปลี่ยนไปเยอะแล้วเพราะไม่เคยเห็นแกเดือดในห้องประชุมแบบที่ผมเป็น (หรือจริงๆแกไม่เคยเดือดวะ แต่ใช้วิธีนี้ในการตำหนิผม) จริงๆมีอีกหลายเรื่องเช่น ทำยังไงให้คนประทับใจ ทำยังให้คนคล้อยตาม ซึ่งเรื่องพวกนี้เราต้องเจอเกือบจะทุกวัน ปฏิเสธนัดเพื่อนยังไงให้มันไม่โกรธ จะให้เพื่อนไปกินร้านบุฟเฟ่ห์ร้านที่เราอยากกินได้ยังไง

ความอ่อนแอของจิตใจมนุษย์

การอ่านหนังสือเล่มนี้นั้นได้บอกวิธีหลายๆอย่างในการทำให้มนุษย์รู้สึกดี รู้สึกสบายใจ รู้สึกมีคุณค่า โดยข้อแรกๆที่คุณ Dale Carnegie เขียนไว้เลยคือ ให้มันมาจาก ใจจริง ตอนแรกก็ไม่เข้าใจว่าทำไมคุณ Dale Carnegie ถึงเขียนไว้อย่างนั้น แต่พออ่านหนังสือเล่มนี้เกือบจบ (ณ ตอนที่เขียน บทสุดท้ายพูดถึงเรื่องสามี ภรรยา เลยยังไม่ได้อ่าน) ก็เลยเข้าใจว่า ทั้งหมดมันคือการเล่นกับจิตใจของมนุษย์ แค่คุณชมหรือพูดอะไรนิดหน่อย คนที่คุณคุยด้วยอาจเปลี่ยนการตอบโต้ชนิดแบบหน้ามือหลังเท้า ตัวอย่างที่แบบ เอาจริงดิ เรื่องนึงคือ การเจรจารวมธุรกิจที่ยากเย็นแสนเข็ญแบบตกลงกันไม่ได้สักที กลับจบแบบง่ายๆโดยฝ่ายหนึ่งบอกว่าชื่อบริษัทที่จะรวมกันชื่อของคุณ แค่นั้นการตกลงง่ายดายขึ้นมาในทันที หรือ พนักงานคนหนึ่งทำงานควงกะประมาณ 12 - 13 ชั่วโมงต่อวัน เขาเรียกร้องให้บริษัทจ้างคนมาทำงานเพิ่มเพื่อช่วยเขา แต่บริษัทใช้วิธีการสร้างห้องให้พนักงานคนนั้นให้ชื่อตำแหน่งเท่ห์ๆ แปะหน้าห้องว่าเป็นห้องของพนักงานคนนั้นแปะป้ายตำแหน่งใหญ่โตให้ หลังจากนั้นพนักงานคนนั้นไม่บ่นอะไรอีกเลย หรือตัวอย่างเกี่ยวกับทหารก็คือ นโปเลียนสร้างยศให้ทหารที่ออกรบให้มียศต่างๆ มีเหรียญกล้าหาญให้ ทำให้พวกทหารพวกนั้นจงรักภักดีอยากไปรบอีก (ดูไปมันก็คล้ายๆกับบางประเทศที่มียศนายพลเยอะซะเหลือเกิน)

จริงๆมันก็เหมือนการเล่นเกมส์นั่นแหละครับ แต่มันไม่ใช่เกมส์ที่เป็นเลข 0 กับ 1 แต่เป็นเกมส์ของมนุษย์ เล่นกับอารมณ์คำพูด สำหรับผมถ้าไม่ได้อ่านหนังสือเล่มนี้การมีคนชม หรือคนพูดดีด้วย ด้วยวิธีต่างๆที่กล่าวมาอาจจะทำให้ผมไม่คิดอะไร แต่พออ่านแล้วรู้แล้วก็จะกลายเป็นว่าคนที่พูดด้วยต้องการอะไร เขากำลังจะเล่นเกมส์กับจิตใจของผม เราควรจะตอบยังไงให้มันดูดีและเล่นเกมส์กลับไปหาเขาด้วยเพราะเราไม่ได้ต้องการอย่างที่เขาต้องการ สุดท้ายจากจะเป็นการสื่อสารให้ได้ผลดีมันจะกลายเป็นการทำสงครามกัน ดังนั้นคุณ Dale Carnegie จึงแนะนำว่า การจะทำอะไรแบบนี้ขอมันมาจาก ใจจริง แต่คำถามคือ เราจะรู้ได้ไงว่ามันมาจาก ใจจริง

สรุป

สำหรับคนอยากสื่อสารกับมนุษย์แล้วได้ผลดีมากขึ้นหรือต้องเจองานที่พบปะกับมนุษย์ผมแนะนำให้อ่านมากๆครับ ส่วนคนที่อยากรู้ความอ่อนแอของจิตใจมนุษย์ ทริคในการเล่นกับจิตใจมนุษย์อันนี้ผมก็แนะนำให้อ่านครับ สำหรับใครอยากเอาไปรองขาโต๊ะก็ซื้อไปรองได้ครับไม่ว่ากันน่าจะรองได้อยู่

เพลงประกอบการเขียน Blog

Vault Plugin Part 2 - Create Plugin

มาเขียน Vault Plugin กันเถอะ : ตอนที่ 2 สร้าง Plugin

ในตอนที่แล้วเราสามารถ Start Vault server ขึ้นมาใช้งานได้แล้ว ตอนนี้เรามาลองสร้าง Vault plugin ขึ้นมาใช้งาน ซึ่งจริงๆมันมีตัวอย่างที่ Web ของ Vault อยู่แล้วตาม Link นี้ ซึ่งก็บอกละเอียดดีแต่มันขาดส่วนที่เกี่ยวกับการอธิบาย Code แล้วจริงๆมันสร้างสั่งผ่าน Rest api ได้ด้วย ซึ่งน่าใช้กว่า cmd เพราะสามารถจัดข้อมูลก่อนส่งได้ ถ้าเป็น cmd ต้องเป็นบรรทัดเดียว (จริงๆทำหลายบรรทัดก็ได้นะ แต่ไม่ค่อยชอบ)

Compile plugin กันก่อน

ขั้นตอนแรกไป Clone ตัว Plugin มาก่อนโดยใช้คำสั่ง

1
2
3
4
5
6
git clone https://github.com/hashicorp/vault-guides.git
cd vault-guides

# Source code มันมีการ Update หลังจากผมเขียน Blog เลยต้องย้ายมา commit
git checkout e0eb4285d8066770bc1f50056c27e2c6e4a989e1

จากนั้นย้ายเข้าไปใน Directory ของ plugin ตัวอย่าง

1
cd vault-guides/secrets/mock

ตรงจุดนี้คือการเอา Docker ของ go มา compile code ให้

1
2
3
4
5
6
7
8
9
10
11
# ทำการสร้าง docker ขึ้นมา โดยสั่งคำสั่งนี้แล้วมันเข้าไปอยู่ในเครื่อง docker ที่เราสร้างสำหรับทำการ compile
docker run -ti --rm -v "$PWD:/go/src/plugin" golang:1.14-buster bash

# ย้ายไป Directory ที่ Source เราอยู่
cd /go/src/plugin

# ทำการสั่ง Build ตัว Plugin
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o vault/plugins/mock cmd/mock/main.go

# ทำการออกจาก Docker
exit

จากนั้นตรวจสอบ directory ของเราจะได้ดังด้านล่าง

1
2
3
4
5
6
7
8
9
10
11
12
.
├── backend.go
├── cmd
│   └── mock
│   └── main.go
├── go.mod
├── go.sum
├── Makefile
├── README.md
└── vault
└── plugins
└── mock

โดยผลลัพธ์จากการ compile จะอยู่ใน ./vault/plugins/mock ให้ copy ไฟล์นี้ไว้นะครับ

Register Plugin เข้าไปใน Vault server

ในการ Register plugin ของ vault นั้นเราจะต้องเอาไฟล์ Plugin ไปวางไว้ใน Directory plugin บน Vault server ซึ่งจากขั้นตอนที่แล้วที่ให้ copy ไฟล์ ./vault/plugins/mock แล้วไปวางไว้ที่ Directory ของ Vault server ในตอนที่แล้ว ซึ่ง path ที่จะให้ไปวางก็คือ plugin/mock

1
2
3
4
.
├── data
├── plugin
│   └── mock

แต่การวางไฟล์ไว้บนเครื่องยังไม่น่าเชื่อถือว่าคุณคือคนทำ plugin นั้นจริงๆ แล้ว plugin นั้นใช่ตัวเดียวกับวางบน server รึเปล่า ดังนั้นเขาเลยใช้ hash เช็คไฟล์ด้วยซะเลย ดังนั้นในการเราต้องค่า hash ของ plugin นั้นด้วย โดยใช้คำสั่ง

1
2
3
4
5
openssl dgst -sha256 mock

# ผลลัพธ์จะได้แบบนี้ แต่ของคุณอาจจะไม่เหมือนของผม เพราะการ compile อาจจะได้ค่าไม่เท่ากันก็ได้
# SHA256(mock)= c0798363e1dd0ec1c1a8cfe0865654cd7d6b6e0f83401d672e43b4dab1202eb8
# ให้ copy c0798363e1dd0ec1c1a8cfe0865654cd7d6b6e0f83401d672e43b4dab1202eb8 เก็บไว้

จากนั้นเราจะทำการ Register Plugin โดยใช้ Rest api ที่ Vault มีให้ โดยรายละเอียด Api สามารถดูได้ที่ Link นี้ โดยผมจะใช้ Postman ในการยิง Http Request ( ท่านสามารถ Download Collection และ Environment ของ Postman ได้ที่นี่)

ยิง Register Plugin

ตัว Body ที่ยิง

1
2
3
4
{
"sha256": "c0798363e1dd0ec1c1a8cfe0865654cd7d6b6e0f83401d672e43b4dab1202eb8",
"command": "mock"
}

ตรวจสอบว่า Register สำเร็จไหม

โดยถ้าสำเร็จจะต้องมี plugin ชื่อ mock โผล่ในส่วนของ Secret engine ครับ

นำ Plugin มาใช้งาน

ในขั้นตอนเราแค่ทำการ Register plugin เข้าไปในระบบ ให้ระบบทราบว่ามี Plugin นี้อยู่ในระบบนะ แต่เรายังไม่ได้เอา plugin มาใช้งาน โดยถ้าจะเอา plugin ไปใช้เราจะต้องเอามันไป Mount ที่ path โดย 1 plugin สามารถ mount ได้หลาย path โดยจะถือว่าเป็นคนละตัวกันเลย โดยรายละเอียดเกี่ยวกับ api ที่ใช้ Mount plugin ไปที่ path สามารถอ่านได้ที่ Link

Mount plugin to path

ทำการ Enable plugin โดยทำตามภาพ

โดยหา Register สำเร็จเมื่อเข้ามาดูหน้า Web จะเห็นว่ามี Path mock/ โผล่มาที่หน้าจอแล้วดังภาพ

ทดสอบ Mock plugin

Mock plugin ที่เราสร้างขึ้นมานั้นมีหน้าที่เก็บค่าที่ส่งเข้าแล้วเก็บไว้ที่ Path ที่ต้องการ โดยตัวอย่างเราจะเก็บข้อมูลที่ path : test โดยเก็บข้อมูล

1
2
3
{
"message" : "Secret value : qtaaNdjaddzhdrjit"
}

จากนั้นลอง Get ข้อมูลก็จะได้ข้อมูล

1
2
3
4
5
6
7
8
9
10
11
12
{
"request_id": "28e109fc-6657-647b-97e8-bf542232bd72",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"message": "Secret value : qtaaNdjaddzhdrjit"
},
"wrap_info": null,
"warnings": null,
"auth": null
}

จบก่อนเขียนมาเยอะละ

สำหรับตอนนี้เราได้เรียนรู้วิธีการสร้าง Plugin (จริงๆเอา plugin ตัวอย่างมาใช้) แล้วก็วิธี Register plugin เข้าไปในระบบ แล้วก็เอา Plugin มา Mount ที่ Path เพื่อใช้งาน ในส่วนของตอนถัดไปเรามาดูเรื่อง Code ของ Plugin ตัวอย่างว่ามันทำงานยังไง แล้วลองเปลี่ยน Plugin เล่นกันดู

เพลงประกอบการเขียน Blog

Vault Plugin Part 1 - Start Vault Server

มาเขียน Vault Plugin กันเถอะ : ตอนที่ 1 Start Vault Server

vault คืออะไร จริงๆมีคนนิยามไว้แล้วแล้วผมก็ไม่เก่งเรื่องนิยามดังนั้นตาม Link นี้ไปดีกว่า แต่สำหรับผม มันคือที่ที่มีไว้เก็บอะไรที่ต้องการให้เป็นความลับ เช่น password , private key , token, secret เป็นต้น โดยเปรียบว่ามันเป็นห้องนิรภัยในธนาคาร ที่คนที่จะเข้าไปเอาของมาได้นั้นต้องผ่านการตรวจสอบสิทธิ์ต่างๆที่เข้มงวดก่อนที่จะเข้าไปได้ ว่าง่ายๆ ถ้าเราเจองานที่ต้องเก็บข้อมูลที่ Sensitive ข้อมูลต้องเก็บเป็นแบบเข้ารหัสลับ ไม่ให้คนที่มีสิทธิ์เข้าเครื่องนั้นสามารถเห็นแล้วนำไปใช้ได้ง่ายๆ เช่น เจ้าหน้าที่เทคนิค เป็นต้นที่ shell เข้าเครื่องนั้นได้

เขาเอา Vault เอาไปใช้อะไรบ้าง

จริงๆผมก็ไม่เคยเอาไปใช้หรอกครับ แต่จากการหาอ่านเขาก็เอาไปเก็บข้อมูลที่เป็นความ (หัวข้อที่แล้วพูดไปแล้ว) ดังนั้นไปอ่านคนที่เขาใช้แล้วมาเขียนให้อ่านกันดีกว่า อันนี้คนของ วงใน เขียนเลย น่าจะอ่านเข้าใจง่ายกว่าที่ผมเขียน แต่ในโลกนี้นั้นไม่มีอะไรกลางๆที่ถูกใจทุกคน มันจะต้องมีความต้องการที่เฉพาะ ซึ่งคำตอบสุดท้ายของการแก้ปัญหานี้คือ เขียนเองจ้า ซึ่งนั่นเป็นที่มาของบทความนี้นั่นเอง

Start vault มาใช้งาน

เนื่องจากยุคนี้เป็นยุคของ Docker ในบทความนี้จะใช้ Vault ที่เป็น Docker จะได้ง่ายต่อการลบสร้างใหม่เวลาเกิดความผิดพลาดหรือ Config ค่าผิด ผมแนะนำให้สร้าง directory สำหรับการทดลองนี้ไว้ด้วยครับ เพื่อจะได้ง่ายในการเก็บข้อมูลของ Vault และเก็บ Plugin โดยมี structure ใน directory ดังนี้

1
2
3
.
├── data
└── plugin
1
docker run -d -v "$PWD/plugin:/vault/plugin" -v "$PWD/data:/vault/data" -p 8200:8200 --cap-add=IPC_LOCK --name=test-vault -e 'VAULT_LOCAL_CONFIG={"listener":[{"tcp":{"address":"0.0.0.0:8200","tls_disable":1}}],"storage":{"file":{"path":"./vault/data"}},"default_lease_ttl":"168h","max_lease_ttl":"720h","api_addr":"http://127.0.0.1:8200","ui":true,"plugin_directory":"./vault/plugin","log_level":"debug","disable_mlock":true}' vault:1.5.4 server

เมื่อ Run เสร็จแล้วให้เข้า Browser แล้วพิมพ์ ip ของเครื่องที่เราทำการ Run docker ของผมคือ 192.168.56.101 เครื่องของคุณอาจจะเป็น 127.0.0.1

1
2
3
4
5
# เข้าผ่าน Browser
http:<YOUR-DOCKER-HOST-IP>:8200/ui

# ของผมคือ
http://192.168.56.101:8200/ui

ในส่วนของ key share กับ key threshold ให้ใส่ 1 ไปก่อนเพราะเราทำแค่คนเดียว จากนั้นกด initialize จากนั้นจะเปลี่ยนมาหน้านี้

ให้ทำการกด Download key ซึ่งไฟล์จะหน้าตาประมาณนี้

1
2
3
4
5
6
7
8
9
{
"keys": [
"53b1b68057e39a27def299445995669c1d7e2fed9e574d609d5a22cb6901d14c"
],
"keys_base64": [
"U7G2gFfjmife8plEWZVmnB1+L+2eV01gnVoiy2kB0Uw="
],
"root_token": "s.lVRVeQTPBJbxPijFKv6ghm21"
}

ซึ่งไฟล์นี้ในขั้นตอน Production ห้ามให้ใครเห็น ห้ามแจกจ่าย เด็ดขาด

แต่อันนี้เป็นตัว Test ของผมเองเลยไม่มีประเด็นอะไร ไม่นานเดี๋ยวมันก็หายไปแล้ว โดยตัวที่จำเป็นคือ keys_base64 ซึ่งเป็นการปลด Lock vault ให้เข้าไปใช้งานได้ ส่วน root_token ใช้ในการเป็น token ไว้ authen ใช้งาน vault

จากนั้นกดไปขั้นตอนถัดไปคือการทำการ Unseal เข้าไปใช้งาน Vault

จากนั้นทำการ Login เข้าไปใช้งาน

ซึ่งถ้าสำเร็จจะเข้ามาที่หน้านี้ ซึ่งถ้ามาถึงแปลว่าคุณสามารถใช้ Vault ได้แล้ว

ตัดจบก่อน

สำหรับตอนนี้เราสามารถสร้าง Vault server ขึ้นมาใช้งานผ่าน Docker ได้เป็นที่เรียบร้อย ในตอนถัดไปเราจะมาสร้าง Custom plugin ของ Vault ขึ้นมาใช้งาน แล้วก็ Register plugin เพื่อลองใช้งานกัน

ไม่เกี่ยวกับ Docker แต่เกี่ยวกับไอดอล

Lara

น้องเป็นไอดอลสายวิชาการแถมน้องเป็นโปรแกรมเมอร์ด้วยนะ ไปติดตาม Page กันได้น้องอัพเดทความรู้ต่างๆเกี่ยวกับ IT แล้วก็เกี่ยวกับ Game ด้วย

Lara

เพลงประกอบการเขียน Blog

ชอบการจิกกัดของเพลงนี้มาก

Spring boot สร้าง ID ให้กับ Log แต่ละ Request

Spring boot สร้าง ID ให้กับ Log แต่ละ Request

เมื่อประมาณ 3 ปีที่แล้วได้ผมเขียนเรื่องเกี่ยวกับ Log ว่ามันควรจะ Log ยังไงต่างๆนาๆ ซึ่งผมก็พยายามทำตาม ตามกำลังที่ตัวเองพอจะทำได้ เพื่อที่เวลาเกิดปัญหาจะได้ตามหาปัญหาที่เกิดได้ง่ายๆ ซึ่งมันก็อยู่ในระดับที่โอเคพอหาได้ แต่ปัญหาเกี่ยวกับการอ่าน Log ก็ยังมีอยู่ไม่ได้หมดไป

1 Request ที่เข้ามามันทำงานหลายที่ใน Application

ปัญหานี้มันเริ่มมาจากการที่เราเขียน Code แยกเป็นส่วนๆ แล้วแบ่งกันไปทำงาน ดังนั้น เวลาทำงาน Code มันส่งไปทำงานหลายส่วน ดังนั้น 1 Request ที่มาจาก Client นั้นมันผ่านไปทำงานหลายส่วน คราวนี้เวลาเกิดปัญหาเนี่ย มันรู้แหละว่าเกิดปัญหาที่ไหน แต่ประเด็นที่มักเจอคือ มันไปผ่านอะไรมาบ้างก่อนหน้านี้ทำให้เกิดเรื่องขึ้น ซึ่งจริงๆ trace log นี่มันบอกว่าผ่านอะไรมาบ้าง แต่มันเป็น Line number of code เวลาดูต้องเปิด code ไล่ดูด้วย อีกทั้งแต่ละส่วนที่ผ่านมันอาจไม่ได้ใช้ Key ที่เราใช้หาเวลาเกิดปัญหาเช่น มันเกิดปัญหาเกี่ยวกับ Order ที่ OrderId เลขนี้ แต่การตัดสินใจต่างๆมันตัดสินใจว่า Order จะดำเนินการได้มันไปอยู่ที่หลายส่วนที่พิจารณา และในบางส่วนมันไม่ได้จำเป็นต้องใช้ OrderId ในกระบวนการการทำงานทำให้เวลาเรา Grep Log ด้วย OrderId Log ในส่วนที่ไม่มี OrderId นี้ก็ไม่ขึ้นมาแสดงให้ดูด้วย ทำให้เวลาไล่ดู Log ต้องใช้ความรู้ความเข้าใจเกี่ยวกับ Flow ของ Program มากขึ้นด้วยเช่น ต้อง Grep ด้วย OrderId จากนั้น Grep ต่อด้วยส่วนที่ใช้ที่เกี่ยวข้องต้องดูเวลาที่ใกล้เคียง ทำให้คนที่ดูแลระบบอาจต้องเป็น Dev ไปด้วย

ตัวอย่าง Log

อันนี้เป็นตัวอย่าง Log ที่ผมจำลองขึ้นมา การทำงานของมันก็ประมาณว่า TestController -> OrderService -> UserService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2020-06-20 20:58:05.855  INFO  --- blog.surapong.example.logproblem.api.controller.TestController : TEST_LOG orderId : 5678 BEGIN
2020-06-20 20:58:05.855 INFO --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 5678 BEGIN COMPLETE ORDER ORDER
2020-06-20 20:58:05.856 INFO --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 5678 Check user userId : a8caf905-4116-44cf-9fc7-572b96b11e96
2020-06-20 20:58:07.850 INFO --- blog.surapong.example.logproblem.api.controller.TestController : TEST_LOG orderId : 1234 BEGIN
2020-06-20 20:58:07.850 INFO --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 1234 BEGIN COMPLETE ORDER ORDER
2020-06-20 20:58:07.850 INFO --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 1234 Check user userId : c75043d4-064a-4c41-8b5d-549ceee72914
2020-06-20 20:58:09.247 INFO --- blog.surapong.example.logproblem.service.UserService : Check user userId : c75043d4-064a-4c41-8b5d-549ceee72914 BEGIN
2020-06-20 20:58:09.247 INFO --- blog.surapong.example.logproblem.service.UserService : Check user userId : c75043d4-064a-4c41-8b5d-549ceee72914 FINISH
2020-06-20 20:58:09.247 INFO --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 1234 END COMPLETE ORDER
2020-06-20 20:58:09.247 INFO --- blog.surapong.example.logproblem.api.controller.TestController : TEST_LOG orderId : 1234 END
2020-06-20 20:58:13.026 INFO --- blog.surapong.example.logproblem.service.UserService : Check user userId : a8caf905-4116-44cf-9fc7-572b96b11e96 BEGIN
2020-06-20 20:58:13.026 INFO --- blog.surapong.example.logproblem.service.UserService : Check user userId : a8caf905-4116-44cf-9fc7-572b96b11e96 FINISH
2020-06-20 20:58:13.026 INFO --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 5678 END COMPLETE ORDER
2020-06-20 20:58:13.026 INFO --- blog.surapong.example.logproblem.api.controller.TestController : TEST_LOG orderId : 5678 END

พอมาอ่าน Log ก็จะเริ่มงงๆว่า เฮ้ย ตกลง orderId 5678 มันไปคู่กับ userId ไหนกันแน่หว่า เพราะเราไม่สามารถเชื่อลำดับได้เพราะ Request มันเข้ามาใกล้ๆกันตอนไปเรียกเช็ค user ตัว UserService ก็ไม่ได้รับตัวแปร orderId มา log ด้วย (ก็มันทำไว้กลางๆ ไม่ได้เจาะจงว่าให้ OrderService ใช้) จริงๆปัญหานี้แก้ได้โดย OrderService ควรจะ Log ด้วยว่า userId ที่จะส่งไปตรวจสอบคืออะไร จะได้รู้ ซึ่งจริงๆคนเขียน Code บางทีเขาก็ไม่รู้หรอกว่ามันจะเกิดปัญหาอะไรบ้าง ส่วนใหญ่เขาก็ Log ประมาณว่ามันกำลังทำอะไรอยู่ บางทีเขาไม่รู้ว่ามันจะเกิดปัญหากับ UserService เลยไม่ได้ Log userId ไว้คู่กับ orderId

ปัญหาแบบนี้มีคนคิดวิธีแก้ไว้แล้ว

ปัญหาของเราคือต้องการมีตัวอ้างอิงให้ Log ของแต่ละ Request จะได้ดู Log ง่ายๆ ซึ่งปัญหาแบบนี้มีหลายคนเจอแน่นอนและแน่นอนชาวโปรแกรมเมอร์ของโลกย่อมลุกขึ้นมาแก้ไขและบอกต่อ โดยผมเป็นคนเขียน java ด้วย spring boot จึงไป search หาวิธีการ config จึงเจอว่ามีคนทำแล้ว โดยเขาใช้ความสามารถของ SLFJ Log และการใช้ Filter ซึ่งเมื่อทำการ Config ตามทำให้เราได้ Log แบบนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2020-06-20 21:36:53.976  INFO 004E4E7AE9FE47E9BBAAAA5A078608F2 --- blog.surapong.example.logproblem.api.controller.TestController : TEST_LOG orderId : 1234 BEGIN
2020-06-20 21:36:53.976 INFO 004E4E7AE9FE47E9BBAAAA5A078608F2 --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 1234BEGIN COMPLETE ORDER ORDER
2020-06-20 21:36:53.976 INFO 004E4E7AE9FE47E9BBAAAA5A078608F2 --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 1234Check user userId : 27dc9962-20c8-4f1a-80fa-fe5235baa43f
2020-06-20 21:36:55.262 INFO 6E81515230B941ECAEE47438BE2C9370 --- blog.surapong.example.logproblem.api.controller.TestController : TEST_LOG orderId : 5678 BEGIN
2020-06-20 21:36:55.262 INFO 6E81515230B941ECAEE47438BE2C9370 --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 5678 BEGIN COMPLETE ORDER ORDER
2020-06-20 21:36:55.262 INFO 6E81515230B941ECAEE47438BE2C9370 --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 5678 Check user userId : 7d6ca7e1-4dfa-4aee-9f75-93eb451fbeab
2020-06-20 21:36:59.203 INFO 004E4E7AE9FE47E9BBAAAA5A078608F2 --- blog.surapong.example.logproblem.service.UserService : Check user userId : 27dc9962-20c8-4f1a-80fa-fe5235baa43f BEGIN
2020-06-20 21:36:59.203 INFO 004E4E7AE9FE47E9BBAAAA5A078608F2 --- blog.surapong.example.logproblem.service.UserService : Check user userId : 27dc9962-20c8-4f1a-80fa-fe5235baa43f FINISH
2020-06-20 21:36:59.203 INFO 004E4E7AE9FE47E9BBAAAA5A078608F2 --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 1234 END COMPLETE ORDER
2020-06-20 21:36:59.203 INFO 004E4E7AE9FE47E9BBAAAA5A078608F2 --- blog.surapong.example.logproblem.api.controller.TestController : TEST_LOG orderId : 1234 END
2020-06-20 21:37:01.167 INFO 6E81515230B941ECAEE47438BE2C9370 --- blog.surapong.example.logproblem.service.UserService : Check user userId : 7d6ca7e1-4dfa-4aee-9f75-93eb451fbeab BEGIN
2020-06-20 21:37:01.167 INFO 6E81515230B941ECAEE47438BE2C9370 --- blog.surapong.example.logproblem.service.UserService : Check user userId : 7d6ca7e1-4dfa-4aee-9f75-93eb451fbeab FINISH
2020-06-20 21:37:01.167 INFO 6E81515230B941ECAEE47438BE2C9370 --- blog.surapong.example.logproblem.service.OrderService : Complete order orderId : 5678END COMPLETE ORDER
2020-06-20 21:37:01.167 INFO 6E81515230B941ECAEE47438BE2C9370 --- blog.surapong.example.logproblem.api.controller.TestController : TEST_LOG orderId : 5678 END

จะเห็นว่าหลัง INFO จะมีเลขยาวๆโผล่ขึ้นมา ซึ่งเลขนี้เป็นเลข ID ของแต่ละ Request นั่นเองคราวนี้เราสามารถ Grep log ด้วยเลข ID นี้ ทำให้ได้ Log ที่เกี่ยวข้องกับ Request นี้ทั้งหมด

เพิ่ม Lib ลงใน pom.xml

เพิ่มตัว Log4j2 ให้กับ Project และบอกให้ spring boot ไม่ใช้ log ของตัวเอง ให้ไปใช้ log ของ log4j2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependencies>

<!-->
Another lib
<-->

<!-- Exclude Spring Boot's Default Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- Add Log4j2 Dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>

สร้าง Filter และ เพิ่ม Filter เข้าไป

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// สร้าง Filter class

public class Slf4jMDCFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain)
throws java.io.IOException, ServletException {

final String mdcTokenKey = "Slf4jMDCFilter.UUID";
try {
final String token = UUID.randomUUID().toString().toUpperCase().replace("-", "");
MDC.put(mdcTokenKey, token);
chain.doFilter(request, response);
} finally {
MDC.remove(mdcTokenKey);
}
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class FilterConfig {

@Bean
public FilterRegistrationBean servletRegistrationBean() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
final Slf4jMDCFilter log4jMDCFilterFilter = new Slf4jMDCFilter();
registrationBean.setFilter(log4jMDCFilterFilter);
registrationBean.setOrder(2);
return registrationBean;
}

}

Config ตัว log4j2.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="LOG_PATTERN">
%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %X{Slf4jMDCFilter.UUID} --- %-50c : %m%n%ex
</Property>
</Properties>
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="ConsoleAppender" />
</Root>
</Loggers>
</Configuration>

ลองสังเกตตรง Slf4jMDCFilter.UUID ในไฟล์ log4j2.xml นั้นจะตรงกับ final String mdcTokenKey = “Slf4jMDCFilter.UUID”; ใน class : Slf4jMDCFilter ซึ่งหากอ่าน Code มันคือการ Register UUID ที่สร้างขึ้นมาไปใส่ใน key ที่ชื่อ Slf4jMDCFilter.UUID ซึ่งเมื่อทำการ Log ตัว Log4j2 จะเอาค่าไปแทนตาม Pattern ที่กำหนดไว้นั่นเอง

สรุป

สำหรับตอนนี้เป็นวิธีการทำให้ Spring boot สร้าง ID ให้กับ Log แต่ละ Request ซึ่งจากวิธีการทำนั้นไม่ยากเลย คราวนี้ก็ช่วยให้เรา Grep log ได้ง่ายขึ้น ในส่วนของภาษาอื่นนั้น เดี๋ยวขอผมไปลองหาดูว่าทำยังไง ถ้าเจอแล้วทำไม่ยากเดี๋ยวผมมาเขียนเป็นตอนใหม่ครับ

แนะนำไอดอล

Toktak

Toktak

เพลงประกอบการเขียน Blog

เป็นเพลงที่ฟังครั้งแรกแล้วรู้สึกชอบมากๆ ทั้งๆดนตรี เสียงประสาน ความหมายของเพลง คือแบบ โอ้ยได้อารมณ์มาก แถมท่อน Rap นี่ใส่มาแบบจริงจังไม่ได้ใส่เล่นๆเอากระแส ส่วน MV นี่ยิ่งโคตรโดนไปใหญ่ เล่นทั้ง JOJO , Forest gump ดูแล้วฮาไปอีก

ref :

https://www.greeneyed.org/post/spring-boot-setting-a-unique-id-per-request/

ชีวิตหน้ามันจะดีจริงๆหรือ

ชีวิตหน้า

ตั้งแต่เด็กความเชื่อหนึ่งที่ถูกสอนให้เชื่อกันมาคือ ชีวิตหน้า โลกหน้า หรือต่างๆนาๆ ต้องหมั่นทำความดีจะได้ไปอยู่สวรรค์ หรือดินแดนอื่นที่ดีงาม มีความเท่าเทียม ทุกอย่างดีไปหมด ตอนเด็กก็เชื่อหมั่นทำความดีต่างๆนาๆเพื่อจะได้ไปโลกหน้าดีๆ ไปอยู่สวรรค์มีแก้วแหวนเงินทองตามความเชื่อง่ายๆเหล่านั้น เมื่อมีเรื่องโลกหน้าก็จะมีเรื่องโลกหลังความตาย ผีสางต่างๆนาๆมากมาย

คำถามที่ตามมา

หลังจากใช้ชีวิตมาหมายปี เริ่มตั้งคำถามเกี่ยวกับโลกหน้าและเริ่มเอามาเทียบกับเหตุการณ์เล้นลับต่างๆที่เกิดขึ้น กฏต่างๆที่เซียนเรื่องต่างๆที่เห็นวิญญาณ เห็นอีกโลก หรือไปเที่ยวโลกหน้ามาแล้ว ซึ่งพอมาคิดก็เริ่ม เฮ้ย มันแปลกๆรึเปล่าวะ

ฆ่าตัวตายเป็นบาปหนา กับ พรหมลิขิต

สองเรื่องนี้ตอนแรกไม่เกี่ยวกันหรอก แต่พอเริ่มคิดแล้วมันมีอะไรขัดแย้งกันมากๆ มาเริ่มที่พรหมลิขิต

พรหมลิขิต

ถ้าเข้าใจไม่ผิดมันคือเรื่องที่กำหนดไว้แล้ว ว่าง่ายๆ ทุกสิ่งทุกอย่างเนี่ยถูกกำหนดไว้แล้ว แม้แต่ตอนที่ผมกำลังจะเขียนบทความนี้ขึ้นมามันก็ถูกกำหนดไว้แล้ว ไอทฤษฎีนี้เนี่ยถูกคนสงสัยมาตั้งนานแล้วเพราะหลายศาสนามักบอกว่าพระเจ้าได้กำหนดทุกสิ่งมาแล้ว แต่เมื่อมีคนเชื่อก็ย่อมมีคนไม่เชื่อและหาคำถามมาหักล้าง โดยคำถามง่ายๆก็คือ หากทุกสิ่งถูกกำหนดมาแล้วแสดงว่าเราไม่เคยมีเจตจำนงเสรี หรือก็คือการที่ผมกำลังเขียนบทความนี้นั้นพระเจ้าได้กำหนดไว้แล้ว การที่ผมชอบไอดอลพระเจ้าก็กำหนดไว้แล้ว ฟังแรกๆก็โอเคแหละครับ แต่ถ้าเป็นเรื่อง อาชญากรรม การฆาตกรรม สงครามต่างๆนาๆ นั้นถูกกำหนดไว้แล้ว อือ มันแปลว่าอะไรวะ ทำไมต้องให้คนดีตาย ทำไมต้องให้โลกโกลาหล ให้คนอยู่อย่างสงบสุขไม่ได้เหรอ ไอคนกำหนดเนี่ยน่าจะโรคจิตระดับหนึ่งเลยถ้ามันเป็นแบบนั้น แต่ก็มีคำค้านออกมาเช่น มันเป็นบททดสอบจากพระเจ้า หรือ พระเจ้ากำหนดเฉพาะเจาะจง แต่กำหนดเฉพาะอะไรที่สำคัญ หรือจุดหมายปลายทางเท่านั้น แต่ก็มีคำถามกลับต่อมาอีกว่า แล้วเรื่องไหนมันระดับสำคัญ ใครจะให้คำตอบในเมื่อมันไม่มีคำตอบ สุดท้ายก็ต้องไปถามพระเจ้าอย่างเดียวว่าสำคัญคืออะไร

ฆ่าตัวตายเป็นบาปหนา

หลายศาสนามักมีคำสอนว่าการฆ่าตัวตายเป็นอะไรที่หนักหนาสาหัสมาก ถ้าศาสนาพุทธก็ต้องฆ่าตัวตายซ้ำๆไปเรื่อยๆจนกว่าจะหมดอายุขัย ถ้าศาสนาอื่นก็เป็นตกนรกไปเลย ซึ่งจริงๆก็ไม่ค่อยเข้าใจเท่าไหร่ว่าทำไมศาสนาถึงไม่อยากให้คนฆ่าตัวตาย แต่ถ้ามองในมุมของการบริหารทรัพยากร การฆ่าตัวตายคือการเสียทรัพยากร ซึ่งในสมัยก่อนมนุษย์คนนึงนั้นมีค่ามากถึงขนาดมีการค้าแรงงานทาส (ในปัจจุบันก็คงไม่ต่าง)ดังนั้นการเสียทรัพยากรไปนั้นย่อมเป็นเรื่องน่าเสียดาย ศาสนาก็เลยตั้งกฏนี้ขึ้นมา แต่หากมองในมุมของเสรีภาพ การฆ่าตัวตายเป็นสิทธิ์ที่สามารถกระทำได้ เพราะชีวิตเป็นของเขาเขาจะฆ่าตัวตาย(โดยไม่ให้ใครเดือดร้อน)ได้

ความขัดแย้ง

  • เมื่อทุกอย่างถูกลิขิตไว้แล้วแสดงว่าการฆ่าตัวตายก็ถูกลิขิตไว้แล้ว แล้วเหตุใดมันจึงเป็นบาปหนาล่ะ
  • เมื่อการฆ่าตัวตายเป็นบาปหนาจึงไม่ให้ทำ ดังนั้นคนเรามีเจตจำนงเสรีในการเลือกดังนั้นพรหมลิขิตไม่มีจริง

ดังนั้นสองเรื่องนี้ ถ้าคุณคิดจะเชื่อควรเลือกเชื่อได้แค่เรื่องเดียวนะครับ คุณไม่สามารถเชื่อทั้งสองเรื่องได้เพราะมันขัดแย้งกัน

ระบบการดูแลความปลอดภัยของโลกหน้า

เรื่องนี้ตอนแรกก็คิดไม่ถึงแต่ช่วงหลังได้ทำงานที่บ้านเลยมีโอกาสนั่งฟังข่าวที่ที่บ้านเปิดดูกันซึ่งมักจะมีข่าวเกี่ยวกับสิ่งลี้ลับเยอะมาก ไม่ว่าจะเป็นตัวตายตัวแทน ผีเจ้าที่ ผีเจ้าป่าเจ้าเขา พอเริ่มฟังบ่อยๆก็เริ่มคิดขึ้นได้ว่า อ่าไอที่พวกผีเวรพวกนี้ทำอยู่มันเป็นอาชญากรรมนะ แล้วโลกหน้าเนี่ยมันไม่มีตำรวจหรือหน่วยงานที่คอยจัดการเลยเหรอ

ตั้งค่าความคาดหวัง

เราลองมาคาดหวังศักยภาพของโลกหน้าในมุมมองของผมกันก่อน จากการถูกพูดให้ฟังอยู่บ่อยๆว่าโลกหน้าเนี่ยสามารถบันทึกเรื่องเวรกรรมหรือสิ่งที่ทำได้อย่างแม่นยำ อีกทั้งเมื่อตายขึ้นมาก็มีคนมารับทันที แสดงว่าเทคโนโลยี Tracking ของโลกหน้าเนี่ยน่าจะระดับสุดยอดเลย

ตัวตายตัวแทน

ตัวตายตัวแทนมันคือการที่มีวิญญาณหรืออะไรสักอย่างที่ตายในที่ที่หนึ่งแล้วไม่ได้ไปผุดไปเกิด จะมาฆ่าคนปกติอย่างพวกเราเพื่อให้เราไปอยู่แทนเขาแล้วเขาจะไปผุดไปเกิด ถ้าเทียบกับโลกของเราคือ ไอวิญญาณเวรพวกนี้กำลังจะก่อคดีฆาตรกรรมนะครับ แล้วหน่วยงานดูแลความปลอดภัยผมขอเรียกว่าตำรวจของโลกหน้าละกัน มันหายหัวไปไหน ทีหน่วยงานมารับคนตายยังมี แต่หน่วยงานจัดการคนที่กระทำความผิดพวกนี้มันหายไปไหนล่ะ ถ้าโลกหน้าดีกว่าโลกนี้จริงมันต้องมีระบบการจัดการที่ดีกว่าโลกนี้สิ ระบบ Tracking ที่ Tracking ทุกการกระทำได้ ทำไม Tracking ไอผีเวรพวกนี้ไม่ได้ล่ะ หรือถ้าระบบ Tracking มันห่วยจริงๆอะ หน่วยงานพวกนั้นไม่มีปัญญาดูรายการผีต่างๆทั้งทาง ทีวี internet หรือช่องทางต่างๆที่บอกว่ามีผีตัวตายตัวแทนออกอาละวาดแล้วไปจัดการเหรอ อันนี้เป็นเรื่องที่ผมคิดแล้วมันดูขัดแย้งจริงๆ

เจ้าหน้าที่ประพฤติไม่ชอบ

ถ้ามองไอตัวตายตัวแทนเป็นอาชญากร คราวนี้ผมก็คิดขึ้นมาว่างั้นพวก เจ้าป่าเจ้าเขา หรือ เจ้าที่ เป็นเจ้าหน้าที่ของโลกหน้า คราวนี้มาลองดู Case พวก เจ้าป่าเจ้าเขาบังตาคน ขังคน ไม่ให้คนออกมาจนตายคาป่า หรือ เจ้าที่ทำร้ายคนที่มาอยู่ คำถามคือคนพวกนี้คือเจ้าหน้าที่ของโลกหน้าแต่มาทำร้ายคนล่ะ ถึงจะบอกว่าคนเหล่านั้นลบหลู่ หรือ ไม่แสดงความเคารพ แต่ถ้าเทียบกับเรื่องจริง คุณไปที่ว่าการอำเภอคุณอาจจะไปวีนใส่พนักงานที่นั่นเพราะแม่งทำงานชุ่ยหรือกวนตีน ซึ่งเจ้าหน้าที่อาจจะไม่พอใจแต่เจ้าหน้าที่ที่นั่นก็คงต้องบริการต่อให้ อีกทั้งต้องไม่ทำร้ายคนที่ไปใช้บริการด้วย เพราะถ้าทำก็ต้องมีปัญหาไม่ว่าจะเป็นปฏิบัติหน้าที่ไม่เหมาะสม แต่ทำไมพอเป็นเจ้าป่าเจ้าเขาเอาคนไปขัง ไปซ่อนกลับไม่โดนลงโทษล่ะ หรือ ถ้าบอกว่าเจ้าที่ เจ้าป่าเจ้าเขาไม่ใช่เจ้าหน้าที่ ดังนั้นไอพวกนี้ก็เป็นพวกอาชญากร ตำรวจที่คอยจัดการหายไปไหนล่ะ

ดังนั้นถ้าเขาเป็นเจ้าหน้าที่เขาทำแบบนี้แปลว่า เจ้าหน้าที่ประพฤติไม่ชอบ แต่ถ้าเขาไม่ใช่ ตำรวจทำไมไม่จัดการคนพวกนี้ แถมอันนี้ง่ายกว่าอีกเพราะมีที่อยู่แน่นอนด้วย เช่น ป่าโน้น ป่านี้

จบการคิดเรื่อยๆเพียงเท่านี้

ในบทความนี้ผมได้เล่าเรื่องที่ผมได้คิดเกี่ยวกับโลกหน้าโดยเอามาเทียบกับโลกนี้ ซึ่งพอมาเทียบๆดูแล้ว โลกหน้ามันอาจจะไม่ดีกว่าโลกนี้ ไปๆมาๆอาจจะแย่กว่าโลกนี้เสียด้วย จริงๆมันมีอีกหลายเรื่องที่ผมคิดแล้วมันแปลกๆเกี่ยวกับโลกหน้าแต่เก็บไว้คราวหน้าดีกว่า เดี๋ยวมันจะยาวเกินไปและไร้สาระไปมากกว่านี้

เพลงประกอบการเขียน Blog

เป็นเพลง OST ของเกมส์ Chrono cross ที่ฟังแล้วรู้สึกตลกมาก ซึ่งเพลงประกอบเกมส์นี้นี่ดีทุกเพลงไม่แพ้ Final fantasy ซึ่งโดยส่วนตัวผมชอบของ Chrono cross มากกว่าเพราะมันฟังสบายๆ ไม่เน้นความอลังการเท่าไหร่

Docker Part 9 - Docker compose 2

Docker Compose 2

ในตอนที่แล้วเราเริ่มใช้ docker-compose แทนคำสั่ง docker run กันแล้วซึ่งจะเห็นได้ว่าถ้าใช้ docker run เป็นจะมาใช้ตัว docker-compose นั้นง่ายมาก เพราะแค่เปลี่ยนวิธีประกาศเท่านั้นเอง ในตอนนี้เรามาลองประกาศหลายๆ container ในไฟล์ docker-compose.yml กันครับ

ลองสร้าง Container หลายตัว

เรามาลองสร้าง container หลายๆตัวใน compose file เดียว ซึ่งทำได้ง่ายมาก

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3'

services :
nico_db :
image: "mysql:5.7"
ports :
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
pin_db :
image: "mysql:5.7"
ports :
- "3307:3306"
environment:
MYSQL_ROOT_PASSWORD: root

โดยจากตัวอย่างไฟล์เราจะสร้าง container ที่เป็น DB ขึ้นมาสองตัว โดยตัวนึง Bind ที่ Port 3306 อีกตัวที่ Port 3307 โดยเราสามารถดูผลลัพธ์การสร้างได้ด้วยคำสั่ง

1
2
3
docker ps
# หรือ
docker-compose ps

โดยตัว docker-compose ps จะแสดงเฉพาะ container ที่สร้างจาก docker-compose.yml โดยจากภาพจะเห็นว่ามี container สร้างขึ้นมาสองตัวโดยชื่อมันจะถูกตั้งโดย docker-compose โดยเอาชื่อ directory ที่ไฟล์ docker-compose.yml อยู่มาต่อกับชื่อ service แล้วต่อด้วยเลข ซึ่งจริงๆเราสามารถกำหนดชื่อให้ container ได้ด้วยการกำหนด container_name ใน docker-compose.yml ตัวอย่างเช่น

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3'

services :
nico_db :
container_name: nico
image: "mysql:5.7"
ports :
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
pin_db :
container_name: pin
image: "mysql:5.7"
ports :
- "3307:3306"
environment:
MYSQL_ROOT_PASSWORD: root

Container ใน Compose คุยกันยังไง

Docker นั้นมีระบบ Network ที่ใช้เชื่อมต่อกัน ซึ่งมีหลายแบบซึ่งในส่วนนี้มีคนอธิบายไว้แล้ว สามารถตาม Link นี้ไปอ่านได้เลย ส่วนที่ผมจะอธิบายในตอนนี้จะเป็นวิธีที่ container ที่ประกาศไว้ docker-compose.yml จะคุยกันยังไง

อันดับแรกเรามาสร้าง container ที่เราต้องการจะคุยกันก่อน

1
2
3
4
5
6
7
8
9
10
11
version: '3'
services :
nico_busybox:
container_name: nico
image: "busybox"
# ตรง command นี้ไม่มีอะไร ผมอยากให้ container มัน run อยู่ไม่ดับหลังจาก start เสร็จ
command: ["tail", "-f", "/dev/null"]
kawjao_busybox:
container_name: kawjao
image: "busybox"
command: ["tail", "-f", "/dev/null"]

image : busybox คือ Image ของ busybox ที่มีคำสั่งพื้นฐานที่ใช้งานบ่อยๆ (มันเลยใช้คำว่า busy สินะ) ของ Linux ไว้ เช่น ls, telnet, ping ซึ่งอันนี้ไม่มีอะไรมากครับผมจะใช้ ping ลอง ping ไปหา container ต่างๆว่าจะต้องเรียกแบบไหน

จากนั้นสั่ง up แล้วจะได้ผลลัพธ์ด้านล่าง

1
docker-compose up -d

คล้ายนี้ลองเข้าไปใน container ของ nico เพื่อจะได้ทดลองการเชื่อมต่อ

1
docker exec -ti nico sh

จากนั้นลองสั่ง

1
2
ping kawjao
ping kawjao_busybox

จากภาพจะเห็นว่าเราสามารถอิงไปหา container kawjao ได้โดยใช้ container_name (kawjao) หรือใช้ service_name (kawjao_busybox) ซึ่งที่มันทำแบบนี้ได้เพราะ docker-compose มันทำให้ โดยถ้าเราไม่ได้ระบุว่า container นี้อยู่ใน network ไหน มันจะ default สร้างวง network ขึ้นมาให้เราและเอาทุก container ไปอยู่ในวง network นั้น โดยของผม docker-compose สร้างให้ตามชื่อ folder ที่ docker-compose.yml อยู่

ลองมาเล่นเกี่ยวกับ network กัน

มาลองเล่นเกี่ยวกับ Network กันครับ โดยผมจะสร้าง network ขึ้นมา 2 วงคือวง siamdream และ meltmallow และ container ขึ้นมา 3 ตัว โดยให้ nico อยู่ network : siamdream ส่วน pin, kawjao อยู่ network : meltmallow

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
version: '3'
services :
nico_busybox:
container_name: nico
image: "busybox"
command: ["tail", "-f", "/dev/null"]
networks:
- siamdream
kawjao_busybox:
container_name: kawjao
image: "busybox"
command: ["tail", "-f", "/dev/null"]
networks:
- meltmallow
pin_busybox:
container_name: pin
image: "busybox"
command: ["tail", "-f", "/dev/null"]
# ส่วนนี้เป็นการบอกว่าให้ container นี้อยู่ network ไหนบ้าง
networks:
- meltmallow

# ส่วนนี้เป็นการประกาศว่า compose นี้มี network อะไรบ้าง
networks:
siamdream:
meltmallow:

คราวนี้มาลองเข้าเครื่อง nico เพื่อดูว่าจะสามารถติดต่อไป pin หรือ kawjao ได้ไหม และลองเข้าเครื่อง kawjao เพื่อดูว่าจะ ping ไปหา nico ได้ไหม

ซึ่งจากการทดลองจะเห็นว่า container ที่อยู่คนละ network กันไม่สามารถ ping หากันได้ แต่ถ้าอยู่ในวง network เดียวกันจะสามารถ ping ไปหากันได้ ซึ่งที่มันเป็นแบบนี้เพราะตัว docker นั้นไปสร้างวง network ให้อัตโนมัติดังภาพด้านล่าง ซึ่งจะเห็นว่าวง Network นั้นอยู่คนละวงกันจริงๆ โดยที่เราไม่ต้องไปกำหนดเอง ซึ่งด้วยวิธีทำให้ Developer สามารถจัด Zone การเชื่อมต่อระหว่าง Container ต่างๆได้โดยไม่ต้องไปกำหนด Network เองเหมือนสมัยก่อน

คราวนี้มาลองเพิ่ม Network เพิ่มเข้าไปแล้วดูว่าจะเป็นอย่างที่ผมบอกไหม

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
version: '3'
services :
nico_busybox:
container_name: nico
image: "busybox"
command: ["tail", "-f", "/dev/null"]
networks:
- siamdream
- glasses_girl
kawjao_busybox:
container_name: kawjao
image: "busybox"
command: ["tail", "-f", "/dev/null"]
networks:
- meltmallow
pin_busybox:
container_name: pin
image: "busybox"
command: ["tail", "-f", "/dev/null"]
networks:
- meltmallow
- glasses_girl
networks:
siamdream:
meltmallow:
glasses_girl:

ซึ่งจากการทดลองจะเห็นว่า container nico สามารถ ping ไปหา container pin ได้แล้วเพราะสอง container นี้อยู่ในวง network : glasses_girl แต่ nico ไม่สามารถ ping ไปหา kawjao ได้เนื่องจากไม่ได้อยู่ network วงเดียวกัน ซึ่งจากตัวอย่างนี้ทำให้เห็นว่าเราจะสร้างวง network ขึ้นมากี่วงก็ได้ตามความต้องการ อยากให้ container ไหนเชื่อมต่อกันได้ก็เอาไปอยู่วงเดียวกัน ถ้าเทียบความเป็นจริงตามเขาก็จะจัดให้เครื่อง app ต่อ database ได้เท่านั้น ส่วนเครื่องขาเว็บพวก apache nginx ก็ต่อได้แค่เครื่อง app ดังนั้น ต่อให้ hack เครื่อง web ได้แต่ก็จะไปไม่ถึงเครื่อง database ต้อง hack ต่อไปให้ถึงเครื่อง app ก่อน ถึงจะ hack ต่อเข้าเครื่อง db ได้เป็นต้น

สรุป

สำหรับตอนนี้ทำให้เรารู้ว่าเราสามารถสร้างหลายๆ container ได้ในไฟล์ compose เดียว และได้รู้การเรียกหา container ต่างๆที่ถูกสร้างใน compose เดียวกันว่าต้องเรียกอย่างไร วิธีการสร้าง network ใน compose file และประกาศให้แต่ละ container อยู่ Network ไหน สำหรับตอนนี้ก็ขอจบเพียงเท่านี้

ไม่เกี่ยวกับ Docker แต่เกี่ยวกับไอดอล

Airi

Airi

เพลงประกอบการเขียน Blog

เพลงนี้ฟังตอนแรกนึกว่าเป็นเพลงรักหวานๆพูดถึงจดหมายจากวันวานเปิดอ่านแล้วรู้สึกหัวใจพองโตเพราะดนตรีมันฟังสบายๆ ฟังครั้งแรกคิดว่าอย่างงั้นจริงๆนะ พอฟังรอบสองตั้งใจฟังเนื้อร้อง เฮ้ย เดี่๋ยวนะเดี๋ยวก่อน… นี่มันอย่างเศร้าเลยนะยิ่งท่อน

หากเปลี่ยนวันให้ย้อนเวลา กลับไป.. ฉันขอทำให้เธอ ไม่เจอฉัน…

โหคือมันต้องรู้สึกเสียใจขนาดไหน

Docker Part 8 - Docker compose 1

Docker Compose 1

ในตอนก่อนๆเราได้เรียนรู้วิธีการสร้าง Container ผ่านคำสั่ง docker run กันมาแล้ว ซึ่งทุกอย่างก็ดูดีเหมือนไม่มีอะไร แต่ปัญหาเกิดขึ้นเมื่อเราต้องการเอา Container หลายๆตัวมาทำงานร่วมกัน ดังภาพ

จากภาพนั้นสมมุติเราต้องเขียน Web application สั่งอาหารลูกค้าโดยต้องแสดงร้านอาหารและอาหารให้ลูกค้า จากนั้นพอเลือกอาหารเสร็จลูกค้าจะสั่ง Order อาหาร ประมาณนั้น ซึ่งเรา Design ออกมาได้ Component ดังภาพ มี Apache เป็น Proxy มี Order app กับ Order db ในการทำงานเกี่ยวกับการสั่งอาหาร Restaurant app , Restaurant db ทำงานเกี่ยวกับการแสดงอาหารและค้นหาอาหาร จากภาพถ้าคิดแบบง่ายๆ เราต้องใช้ Container 5 ตัวในการทำงาน

โปรแกรมเมอร์ไม่ชอบทำอะไรยุ่งยากซ้ำๆ

แค่จากภาพเราก็พอจะรู้แล้วว่าเวลาจะต้อง Run container 5 ตัวในการทำงาน ซึ่งต้องมาสั่ง 5 ครั้งไม่ว่าจะต้องสั่ง run ครั้งแรก หรือ ตอนสั่ง start ด้วยความน่าเบื่อนี้ส่วนใหญ่ก็จะทำ script กัน แต่ด้วยความน่าเบื่อที่ต้องทำ script อีกก็เลยมาเปลี่ยนมาทำสิ่งที่เรียกว่า docker-compose file แทน docker-compose file คือไฟล์ที่มี syntax แบบ yml ซึ่งไฟล์จะอยู่ในลักษณะการ Config ซึ่งก็คือการบอกว่า ต้อง run container ไหนบ้าง จาก image ไหน bind port อะไรยังไง ซึ่งพอเราเขียนไฟล์นี้เสร็จ เราสามารถสั่งงานโดยใช้ docker-compose ตัว docker-compose จะเข้าไปอ่าน config เหล่านั้นและแปลงเป็นคำสั่งพวก docker run ให้เราทั้งหมด

จะเห็นว่าจากต้อง run 5 command กลายเป็นเขียนไฟล์ config กำหนด แล้วกด run คำสั่งเดียว เขียนเสร็จแล้วสามารถ share ไปให้คนอื่นใช้ต่อได้ด้วย โดยไม่ต้องกลัวว่าอีกเครื่องจะ run script ได้ไหม ขอแค่เครื่องนั้น ลง docker-compose ก็พอ

Install docker compose กันก่อน

วิธีลง docker-compose นั้นมีวิธีบอกอยู่แล้วแถมเป็น official ด้วย โดยสามารถดูได้ตามนี้เลย https://docs.docker.com/compose/install/ โดยใครใช้ OS อะไรก็ใช้แบบนั้นที่เขาแนะนำได้เลย

Docker run ใช้แบบนี้ Docker compose ต้องใช้ยังไง

เรามาเริ่มใช้ docker-compose กันเลย โดยจากที่ผมอธิบายไป docker-compose ต้องการไฟล์ config ที่บอกว่าต้องสร้าง container อะไรบ้างแล้วมันจะไปแปลงเป็นไปคำสั่ง docker run ให้ ดังนั้นถ้าเรามีคำสั่ง docker run ในหัว เราก็จะต้องแปลงมันไปอยู่ในรูปแบบของ docker-compose file เพื่อให้ทำงานอย่างที่เราต้องการ อันนี้อาจแปลกๆ แต่จริงๆไม่แปลก เพราะเราเป็น Programmer เรารู้วิธีการสั่งงานแบบนึง ทีนี้เราอยากจะไปใช้เครื่องมืออีกแบบแต่อยากให้ผลเหมือนเดิม เราก็เลยต้องเทียบว่าวิธีเก่าทำแบบนี้ วิธีใหม่ทำยังไงให้ได้ผลแบบเก่า เป็นวิธีคิดและวิธีทำง่ายๆไม่แปลกสำหรับ Programmer

สมมุติผมอยากสร้าง container : mysql ขึ้นมาด้วยคำสั่ง docker run แบบนี้

1
docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root mysql:5.7

เราจะสั่งให้ทำงานด้วย docker-compose ยังไง

ทำการสร้างไฟล์ docker-compose.yml

จาก docker run ด้านบน เราสามารถแปลงให้มาอยู่ใน docker-compose.yml ได้ดังนี้

1
2
3
4
5
6
7
8
9
version: '3'

services :
my_db :
image: "mysql:5.7"
ports :
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root

จากนั้นทำการสั่งย้าย current directory ไปอยู่ที่เดียวกับที่ไฟล์ docker-compose.yml อยู่จากนั้นสั่ง

1
docker-compose up

บนหน้าจอจะขึ้นประมาณนี้จะเห็นว่ามันคล้ายๆสั่ง docker run เลยแต่มีสีสันเพิ่มเข้ามา

อธิบาย docker-compose.yml

จริงๆถ้าใครเคยสั่ง docker run จนชิน พอมาเห็นไฟล์ docker-compose.yml จะเข้าใจทันทีเลยว่าจะเปลี่ยนจาก docker run เป็น docker-compose.yml ยังไงโดยแทบไม่ต้องอธิบายเลย

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '3'
# ตรง version คือบอกว่าเราใช้ docker-compose file version อะไร อันนี้มีผลมากนะครับเพราะบาง
# config จะมาใน version ใหม่ๆ บาง config ก็ถูกยกเลิกใน version ใหม่


services :
# ส่วนนี้คือการบอกว่าถึงส่วนที่จะ config บอกว่าต้อง run container อะไรบ้าง

my_db :
# ตรง my_db คือชื่อ service หรือมองว่า ถ้าจะอ้างถึง container กลุ่มนี้ให้ใช้ชื่อว่า my_db

image: "mysql:5.7"
# image ตรงนี้บอกว่าจะ container นี้ run ด้วย image อะไร
ports :
- "3306:3306"
# ports ตรงนี้บอกว่า จะให้ container นี้ bind port อะไรกับเครื่องจริงบ้าง เทียบเท่ากับ -p
environment:
MYSQL_ROOT_PASSWORD: root
# environment ตรงนี้เป็นการบอกว่า containter นี้จะมี environment อะไรบ้าง เทียบเท่ากับ -e

คำสั่งอื่นๆที่ใช้บ่อยใน docker-compose

  • docker-compose down

อันนี้คือสั่งลบ container ทั้งหมดที่เราประกาศไว้ใน docker-compose.yml เทียบเท่ากับ docker rm -f

  • docker-compose start

อันนี้คือสั่ง start container ทั้งหมดที่เราประกาศไว้ใน docker-compose.yml เทียบเท่ากับ docker start

  • docker-compose stop

อันนี้คือสั่ง stop container ทั้งหมดที่เราประกาศไว้ใน docker-compose.yml เทียบเท่ากับ docker stop

สรุป

สำหรับตอนนี้ขอตัดจบเพียงเท่านี้เพราะรู้สึกว่าเริ่มจะเยอะละ ในตอนนี้เราได้รู้ว่า docker-compose คืออะไร มีไว้ทำไม วิธีเขียน docker-compose.yml วิธีการเรียกใช้งานไม่ว่าจะสร้าง ลบ start stop ในตอนต่อไปเราจะลองสร้าง docker-compose.yml แบบที่กำหนดให้มี container หลายตัว ทำให้แต่ละตัวเชื่อมต่อกัน

ไม่เกี่ยวกับ Docker แต่เกี่ยวกับไอดอล

Toktak

คราวนี้มาเป็นไอดอลแนว Sexy หน่อย เป็นไอดอลที่เห็นตอนแรกแล้วไม่รู้สึกอะไร แต่พอดูบ่อยๆค่อยๆเห็นหลายๆลุคนี้แบบ โอ้ยน่าสนใจจริงๆ

Toktak

เพลงประกอบการเขียน Blog

อีกเพลงใน RE MV Project ของทาง Grammy เป็นอีกเพลงที่ได้ยินทำนองแล้วจะอ๋อทันที โดยตอนดูรายการผู้กำกับบอกว่าผู้หญิงในแต่ละยุคแต่ละสมัยว่าเป็นแบบไหน

Docker Part 7 - Create Image 3

สร้าง Image กันต่อ

จากตอนที่แล้วเราได้รู้ COMMAND ต่างๆเกี่ยวกับ Docker file ไปแล้ว ตอนนี้เราจะมาสร้าง Image แบบตั้งแต่เริ่มจนได้ Image ไปใช้งาน

สร้าง Web application จากภาษา Go

เรามาลองสร้าง web application จากภาษา go กัน อันนี้เลือกภาษา go เพราะมันต้อง compile code จริงๆอยากจะเขียนภาษา Java แต่เดี๋ยวมันจะงงเพราะมีพิธีกรรมมากมายเลยเอาภาษา go ละกันไฟล์เดียวจบเลยจะได้เป็นตัวอย่างง่ายๆ

ตัวอย่าง Code ผมเอามาจาก https://yourbasic.org/golang/http-server-example/

สร้าง Directory ใหม่ขึ้นมาแล้วสร้างไฟล์ชื่อ server.go ใน directory ที่สร้างขึ้นมาใหม่ ของผมสร้างที่

/root/share_docker/test_go ไฟล์ server.go ผมจะอยู่ที่ /root/share_docker/test_go/server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"net/http"
)

func main() {
http.HandleFunc("/", HelloServer)
http.ListenAndServe(":8080", nil)
}

func HelloServer(w http.ResponseWriter, r *http.Request) {

fmt.Printf("Hello, %s!", r.URL.Path[1:])
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])

}

ตัว Source code นี้ไม่มีอะไรมากไปกว่ารับเปิด Port 8080 แล้วแสดงข้อมูลออกทางหน้าจอ คราวนี้ลอง run โปรแกรมดูครับ ถ้าเครื่องภาษา go run server.go ครับ

จากนั้นสร้าง Dockerfile ขึ้นมาครับ

1
2
3
4
5
6
7
8
9
10
FROM golang:rc-buster

WORKDIR /app
ADD server.go /app

# เป็นคำสั่งที่ใช้ในการ compile source code ให้กลายเป็น binary file ที่สามารถสั่ง run ได้เลย
RUN RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

CMD ./main

จากนั้นสั่งสร้าง image ด้วยคำสั่ง

1
docker build -t test-go .

จากนั้นลองทำการ Run docker ขึ้นมาด้วยคำสั่ง

1
docker run -d --rm -p 8080:8080 test_go

จากนั้นลองเข้าผ่าน Browser เพื่อตรวจดูว่าทำงานได้จริงไหม

ทุกอย่างดูเหมือนดีแต่มันก็ยังไม่ดีในบางแง่มุมซึ่งแง่มุมนั้นก็คือขนาดของ Image ครับ โดยหากเราสั่ง docker images จะเห็นข้อมูล Image ที่อยู่บนเครื่องเราดังภาพ

ซึ่งจะเห็นว่า Image มีขนาดใหญ่ถึง 843 MB ซึ่งใหญ่มากๆ ซึ่งข้อเสียของมันคือเสียเวลาในการ Download Image ครับ โดยหลักการทำงานของ Docker คือเครื่องที่ Run docker ต้องมี Image ก่อนถึงจะสร้าง Container ได้ซึ่งถ้า Image ใหญ่ก็ต้องเสียเวลาโหลด ซึ่งไม่ค่อยเหมาะเวลาที่เราทำงานบนระบบ Cloud หรือ Resource ที่โยกย้ายได้ ตัวอย่างเช่น ถ้าทำจะทำ Cluster ไว้ Run docker เพื่อให้เราสามารถกระจาย Container ไปได้หลายเครื่องเพื่อให้ใช้ Resource ได้อย่างเต็มที่และลดความเสี่ยงที่ถ้าเครื่องนึงพังก็สามารถย้าย Container ไปไว้ที่เครื่องอื่นใน Cluster ได้ ซึ่งในปัจจุบันเขาใช้ kubernetes หรือ Docker swarm ในการจัดการ ดังนั้นเวลาเราเพิ่มเครื่องเข้าไปใน cluster กว่าเครื่องใหม่ที่เข้ามาใน Cluster มันจะพร้อมให้ Container มา Run บนเครื่องมันก็จะนานเพราะต้องเสียเวลาไปโหลด Image มา

อ่านเพิ่มเติมเกี่ยวกับ Kubernetes และ Docker swarm

เหล่าโปรแกรมเมอร์และนักออกแบบได้แก้ปัญหานี้แล้ว

ปัญหาที่ผมเล่าไปเมื่อกี้นั้นเหล่าโปรแกรมเมอร์และนักออกแบบเขาได้แก้ปัญหาเหล่านี้ไว้แล้วซึ่งนั่นก็คือ ทำ Image ที่มีไว้สำหรับ Run program อย่างเดียว ตัวอย่างเช่น ถ้าจะ Run java application Image นั้นก็ควรจะมีแค่ JRE (ไม่ต้องเป็น JDK) ไม่ต้องลงโปรแกรมอื่นๆที่ไม่ได้ใช้เช่น vim vi nano netstat ดังนั้น Image นั้นก็จะมีขนาดเล็กลง แต่แค่นั้นยังไม่พอมีคนเอาตัว Distro linux ที่ชื่อ alpine ซึ่งมีขนาดเล็กมากๆ มาเป็น Base image ด้วยทำ Image มีขนาดเล็กลงไปอีกแบบจากบางที 200 MB ก็จะเหลือแค่ประมาณ 6 MB ดังนั้น ถ้าจะทำ Image ที่ไว้ใช้สำหรับ Run อย่างเดียวผมแนะนำให้หา tag ที่มีคำว่า alpine อยู่ด้วย เช่น Nodejs ก็มี image ที่เป็น alpine ให้เราไว้ทำ Image สำหรับ Run อย่างเดียว

ลองทำ Image ใหม่

เราลองมาทำ Image ใหม่โดยใช้ Image tag : alpine มาใช้เป็น base image

1
2
3
4
5
6
7
8
FROM golang:alpine

WORKDIR /app
ADD server.go /app

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

CMD ./main

จากนั้นลอง build image ดูครับ ซึ่งจากการ build ของผม ผมทำ tag ต่างกัน โดยถ้าทำจาก image ธรรมดาจะใช้ tag: normal ส่วนทำจาก image alpine จะใช้ tag: alpine ซึ่งจากการ build ของผมจะเห็นว่าขนาด Image ต่างกันเป็นอย่างมาก

ยังทำได้ดีกว่านี้อีก

เนื่องจากภาษา golang นั้นเป็นภาษาที่ทำการ compile source code ให้กลายเป็น binary file ให้สามารถไปวางบนเครื่องนั้นแล้ว run ได้เลย ดังนั้นถ้าเรา compile source code บน image alpine แล้วไปวางบน image เปล่าๆ (image เปล่าๆคือ scratch) เลยก็จะได้ image ที่จะเล็กมากๆ ปัญหาคือมันทำได้เหรอวะ คำตอบคือได้ครับ เขาจะเรียกการทำแบบนี้ว่า multi-stage builds โดยวิธีการเขียนนั้นก็ไม่ได้แตกต่างจาก Dockerfile ที่เราเคยทำเลย เราไปดูจาก code ก็น่าจะเข้าใจเลย

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM golang:rc-buster as builder
# ตรง as builder ที่เพิ่มเข้ามาเพื่อบอกว่า Image ที่สร้างด้วย FROM นี้เวลาจะเรียกใช้มันด้วยชื่ออะไร

WORKDIR /app
ADD server.go /app

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

FROM scratch
# FROM ที่อยู่ล่างสุดคืออันที่เราจะเอาไปใช้

WORKDIR /
COPY --from=builder /app/main /main
# ตรง --from=builder คือการอ้างถึง image ตัวที่ชื่อ builder ที่เราสร้างไว้ข้างบน
# รวมกันก็คือ เราจะเอาไฟล์ main ที่อยู่ใน /app/main ของ builder ไปวางไว้ใน image ที่เรากำลังจะสร้างที่ path : /main

CMD ["./main"]

จากนั้นทำการ build image โดยของผมจะตั้งชื่อ tag : scratch จากนั้นลองมาดูขนาด image แล้วจะเห็นว่าเหลือแค่ประมาณ 7 MB กว่าๆ

จากนั้นลองทำการ run image ดูครับ ว่ามันสามารถทำงานได้ไหมและสามารถเข้าใช้งานผ่าน Browser ได้หรือไม่

1
docker run --rm -d -p 8080:8080 test-go:scratch

แต่วิธีการทำแบบนี้ทำได้แค่กับบางภาษานะครับ อย่าง Java เนี่ยไม่สามารถทำแบบนี้ได้เพราะ Java ต้องการ JVM ในการทำงานซึ่ง image : scratch นั้นไม่มี JVM ไม่สามารถทำงานได้ ทำได้มากสุดคือการ compile บน JDK แล้วไปวางบน JRE alpine

สรุป

ในตอนนี้เราได้รู้เกี่ยวกับการ Build image แบบ multi-stage builds เพื่อให้เราสามารถได้ image ให้มีขนาดเล็กลง ซึ่งจริงๆในการสร้าง Image นั้นมี Best practice ในการทำอยู่โดยสามารถตามไปอ่านได้ตาม Link ต่อไปนี้

http://www.somkiat.cc/summary-from-dockerfile-guildline/

https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/

ตอนนี้คงเป็นตอนสุดท้ายเกี่ยวกับการสร้าง Image แล้วครับ เพราะผมรู้แค่นี้ ฮ่าๆๆๆๆๆ ตอนต่อไปคงจะเป็นเรื่อง Docker compose

ไม่เกี่ยวกับ Docker แต่เกี่ยวกับไอดอล

Yayee

น้องน่ารักมาก ตอนแรกนึกว่าผมสั้นจะห้าวๆ แต่กลายเป็นสาวหวานซะงั้น ฮ่าๆๆๆๆๆๆๆๆ

Yayee

เพลงประกอบการเขียน Blog

เพลง แค่ได้คิดถึง ของ ญารินดา ที่ Grammy มี RE MV โปรเจค คือเอาเพลงเก่ามาทำ MV ใหม่ โดยตีความใหม่ ซึ่งดูแล้วนี่เศร้าจริงๆ เวอร์ชั่นเก่านี่ดูแล้วเศร้าแบบจุกๆที่บรรยายลำบาก แต่เวอร์ชั่นนี้นี่คือเศร้าแบบอยากร้องไห้ เวอร์ชันนี้มีพี่ญารินดามาประกอบ MV ด้วย

Docker Part 6 - Create Image 2

สร้าง Image กันต่อ

จากตอนที่แล้วเรารู้วิธีการสร้าง Image กันแล้วซึ่งเป็นการใช้ Dockerfile แล้วก็ใช้คำสั่งต่างๆใน Dockerfile ไม่ว่าจะเป็น FROM RUN ADD แต่เรายังขาดคำสั่งสำคัญอยู่ซึ่งมันสำคัญมาก (แล้วทำไมไม่พูดตอนที่แล้ว ฮ่าๆๆๆๆ) ซึ่งมันคือคำสั่ง CMD และ ENTRYPOINT ซึ่งเป็นคำสั่งซึ่งกำหนดว่า container ที่สร้างจาก Image พอสร้างเสร็จแล้วจะให้เขาทำงานด้วยคำสั่งอะไร

CMD

มาเริ่มที่คำสั่ง CMD กันก่อน อันนี้เราลองไปดู Image คนอื่นกันก่อน

Image httpd เขา Show dockerfile ของเขาให้ดูประมาณนี้ https://hub.docker.com/layers/httpd/library/httpd/latest/images/sha256-1fa8b506b23df25b3e2570cd27eb7a88c6d1622e749b7931e9a7f8390d17777b

จากภาพจะเห็นว่า CMD ที่กรอบแดงไว้เรียกใช้คำสั่ง httpd-foreground ซึ่งเมื่อเราลองสั่ง

1
docker run --name test_httpd httpd

จะได้ดังภาพ

ซึ่งตรงกับที่บอกไว้ในคำสั่ง CMD ตรงนี้อาจจะบอกว่ามั่วรึเปล่า งั้นเรามาเปลี่ยน CMD กันดูว่าถ้าเราทำการแก้ไข CMD กันดูแล้วดูว่าจะมีการเปลี่ยนเปลงหรือไม่ โดยทำการสร้างไฟล์ Dockerfile ขึ้นมาโดยใส่คำสั่งในการสร้าง image ไว้ดังนี้

1
2
FROM httpd
CMD ["echo", "hello world Matilda"]

จากนั้นสั่งทำการสร้าง Image

1
docker build -t test_change_cmd .

จากนั้นทำการสั่งสร้าง container

1
docker run --name test_container  test_change_cmd

ซึ่งผลลัพธ์จะเป็นดังภาพซึ่งจะเห็นว่า container ของเราทำการแสดงผล hello world Matilda ออกมา ซึ่งตรงกับที่เราสั่งไว้ และจะเห็นด้วยว่า container ของเราจะไปอยู่ในสถานะ stop ซึ่งมันก็ปกติเพราะมันทำงานคำสั่ง echo hello world Matilda เสร็จแล้ว ทำให้ container ทำการหยุดทำงาน ต่างจาก image httpd สั่งให้ใช้คำสั่ง httpd-foreground ซึ่งเป็นคำสั่งที่ทำงานแบบ infinity loop คือทำไปเรื่อยๆไม่มีจุดจบทำให้ container นั้นไม่มีวันหยุดทำงาน

ENTRYPOINT

ENTRY POINT ก็คล้ายๆกับ CMD เลยครับ คือจะให้ Container ที่สร้างจาก Image นี้ทำการ run คำสั่งอะไร ถึงตรงนี้ผมบอกเลยว่าคุณจะงงแน่นอน แต่ขอให้ลองทำครับขั้นแรกเรามาสร้าง Dockerfile โดยมีวิธีการสร้าง Image ดังนี้

1
2
FROM httpd
ENTRYPOINT ["echo", "hello world Matilda"]

จากนั้นลอง build image ด้วยคำสั่งนี้ดูครับ

1
docker build -t test_entrypoint .

จากนั้นลองทำการ container ด้วยคำสั่งนี้ดูครับ

1
docker run --name container_entrypoint test_entrypoint

ซึ่งจะได้ผลลัพธ์ดังภาพ ซึ่งจะเหมือนกับ CMD เลย ซึ่งทั้งคำสั่ง CMD และ ENTRYPOINT เป็นการบอกว่าจะ Container ที่สร้างขึ้นมาจาก Image จะทำงานด้วยคำสั่งอะไร แต่มันมีข้อแตกต่างกันนิดหน่อยซึ่งจะอธิบาย ณ บัดนี้

CMD vs ENTRYPOINT

ทั้งสองทำงานคล้ายๆกันแต่แตกต่างกันนิดหน่อย เราลองมาเล่นคำสั่ง docker run ไปทีละคำสั่งครับ

1
2
3
4
docker run --rm test_change_cmd 
docker run --rm test_entrypoint

# --rm เป็น option ของ docker run ที่จะทำการลบ container ลงทันทีเมื่อ

ได้ผลลัพธ์เหมือนกัน คราวนี้มาลอง

1
2
docker run --rm test_change_cmd Harupiii
docker run --rm test_entrypoint Harupiii

คราวนี้จะไม่เหมือนกันละ จากภาพจะเห็นว่าสั่งด้วย image ที่ใช้ CMD จะ error แต่ที่ใช้ ENTRYPOINT จะไม่ Error คราวนี้เรามาลองดูแบบละเอียดกันครับ

1
2
3
4
5
6
7
8
9
docker run --rm test_change_cmd Harupiii
docker run --rm test_entrypoint Harupiii

# Harupiii เป็นค่าที่เราใส่ไปหลังชื่อ Image โดยคำสั่ง docker run จะเอาค่าไปจัดการยังไงต่อนั้นมัน
# จะตามเงื่อนไขว่าเป็น CMD หรือ ENTRY POINT

# ถ้า Image นั้นใช้ CMD สิ่งที่อยู่หลัง Image ทั้งหมดจะไปอยู่แทน CMD ของ Image ว่าง่ายๆ container ตัวนี้ถูกบังคับให้ run ด้วยคำสั่ง Harupiii นั่นเอง ซึ่งมันทำให้เกิด Error ขั้นนั่นเอง เพราะไม่มีคำสั่งที่ชื่อ Harupiii นั่นเอง

# ถ้า Image นั่นใช้ ENTRYPOINT สิ่งที่อยู่หลัง Image จะถูกเอาไปต่อหลังคำสั่งที่อยู่ใน ENTRYPOINT จากตัวอย่างอันนี้เราจะได้ว่า container จะ run ด้วยคำสั่ง echo hello world Matilda Harupiii ด้วยเหตุนี้มันเลยไม่ Error

เพื่อให้เข้าใจมากยิ่งขึ้นเรามาลองเล่นแบบนี้ดู

1
2
docker run --rm test_change_cmd ls
docker run --rm test_entrypoint ls

คราวนี้ไม่ Error ทั้งคู่ โดยฝั่ง CMD จะใช้คำสั่ง ls ซึ่งทำการ list file and directory ในปัจจุบันออกมา ส่วนฝั่ง ENTRYPOINT ยังคงเป็นการ echo คำออกมาแต่เป็นการเปลี่ยนเป็น hello world Matilda ls

แล้วจะใช้อะไร

ถ้าเอาง่ายๆก็คือ ถ้าใช้ CMD คือเราอยากให้ Container ที่ Run จาก Image นั้น run คำสั่งที่เหมือนกันไม่ต้องมีการ Customize ตัวคำสั่งที่อยาก run เช่นอย่าง image httpd นี้เขาใช้ CMD คือเขาอยากให้ Image นี้ run ด้วยคำสั่ง httpd-foreground เท่านั้น ส่วนการ config อื่นๆให้ไปทำกันที่ Environment หรือการ mount volume file config เอา แต่ถ้าใช้ ENTRYPOINT อันนี้คือเขาต้องการให้สามารถ customize คำสั่งตอน run container ได้ประมาณว่า run โดยใช้ parameter อะไรบ้างเป็นต้น โดยเขาจะใช้วิธีการสร้างไฟล์ .sh ขึ้นมาสักตัว ประมาณนี้ โดยตัวอย่างเราจะทำเป็นไฟล์ชื่อ entrypoint.sh จากนั้นในไฟล์ entrypoint ก็รับ parameter เข้ามาเพื่อให้ทำอะไรบางอย่าง เช่นตัวอย่างผมจะรับ parameter เข้ามาเพื่อทำอะไรบางอย่างที่แตกต่างกันเช่น

ไฟล์ entrypoint.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if [ "$1" = "" ] 
then
echo "Case no parameter"
elif [ "$1" = "infinity" ]
then
echo "Case run infinity loop"
while :
do
echo "IN LOOP"
sleep 1
done
else
echo "Case other"
fi

อันนี้คือเราจะรับ parameter ให้กับ entrypoint.sh โดยถ้า parameter ตัวแรกที่ส่งเข้ามาเป็นคำว่า infinity จะทำการ Run แบบ infinity loop

ส่วน Dockerfile จะเป็น

1
2
3
FROM httpd
ADD entrypoint.sh ./
ENTRYPOINT ["bash", "/usr/local/apache2/entrypoint.sh"]

จากนั้นสร้าง image ด้วยคำสั่ง

1
docker build -t test_entrypoint .

จากนั้นลองใช้ docker run แบบต่างๆดังนี้

1
2
3
docker run --rm  test_entrypoint
docker run --rm test_entrypoint nico
docker run --rm test_entrypoint infinity

สรุป

สำหรับตอนนี้เรามาทำความเข้าใจคำสั่งสองคำสั่งคือ CMD และ ENTRYPOINT ซึ่งเราได้ลองใช้งานทั้งสองคำสั่งและความแตกต่างของแต่ละคำสั่ง ซึ่งน่าจะสามารถนำไปประยุกต์ใช้งานในการสร้าง Image ได้ในระดับที่เอาไปใช้ได้ในงานจริงแล้ว (ผมรู้แค่นี้ก็เอาไปขึ้น Production ได้แล้ว) สำหรับตอนนี้ขอจบลงเพียงเท่านี้ครับ

โปรโมท Page

ผมทำ Page บน Facebook แล้วนะครับ ใครสนใจรับการแจ้งเตือนเวลาผมอัพเดท Blog ก็ไปกดติดตามกันได้ครับ กดตามลิ้งไปได้เลย Facebook : Normal Programmer

ไม่เกี่ยวกับ Docker แต่เกี่ยวกับไอดอล

Matilda

Matilda

Harupii

Harupii

เพลงประกอบการเขียน Blog

เนื้อเพลงนี่แบบใช่เลย ไปร้องเพลงให้สาวฟังก็ไม่รู้จะป็นยังไง ชอบตรงการเล่นกับคำว่าหมื่นล้านเพลง เวอร์ชันต้นฉบับของพี่บิลลี่ โอแกน เวอร์ชันนี้ละมุนแบนด์เอามา cover ร้องแข่งในรายตำนานหมู่ คือร้องสด เล่นกีตาร์สด แบบ โอ้ยฟินโคตรๆ

Docker Part 5 - Create Image

สร้าง Image ของตัวเองใช้กัน

ในตอนก่อนๆเราได้รู้เกี่ยวกับการใช้งาน Docker สร้าง ลบ แก้ไข Container กันมาแล้ว ในตอนนี้เราจะมาสร้าง Image มาใช้กันเองบ้าง ซึ่งเมื่อสร้างได้แล้วจะเริ่มเข้าใจว่าเราจะเอามันไปใช้งานยังไงในงานจริง

Image มันคืออะไร

Image ก็คือตัวต้นแบบของ container เมื่อเราสร้าง container จาก image ไหน เราจะได้ container ที่หน้าตาเหมือนกัน (แต่เป็นคนละตัวกัน) ออกมา ถ้ามองง่ายๆคือ Image คือ พิมพ์สร้างเยลลี่ครับ ส่วน container คือเยลลี่ที่เราสร้างมาจากพิมพ์ครับ หากคำอธิบายของผมยังไม่เข้าใจ ลองไปอ่านของคุณ Rachata Tongpagdee หรือของท่านอื่นๆดูครับ

First Image

ในการจะสร้าง Image นั้นเราจะต้องสร้างไฟล์ที่ชื่อ Dockerfile ขึ้นมาครับ โดยในเนื้อไฟล์มีหน้าตาประมาณนี้ก่อนครับ

สร้าง Image

1
FROM httpd:2-alpine

จากนั้นย้าย current directory ไปที่ directory ที่ Dockerfile อยู่จากนั้นสั่งตามคำสั่งนี้ครับ

1
2
3
4
docker build -t first-image .

# docker build คือคำสั่งที่ทำการสร้าง Image ขึ้นมา โดยทำการดูวิธีการสร้าง Image จากไฟล์ Dockerfile
# -t เป็น parameter ที่บอกว่า image นี้มีชื่อว่าอะไร โดยจากตัวอย่างจะตั้งชื่อว่า first-image

โดย Run แล้วจะได้ตามภาพครับ

ลองตรวจสอบว่าเราสามารถสร้าง Image ขึ้นมาได้จริงไหมโดยสามารถสั่ง

1
2
3
docker images

# docker images เป็นคำสั่งที่แสดง Image ทั้งหมดที่มีอยู่ในเครื่องเราตอนนี้

จากภาพจะเห็นว่า image : first-image จะโผล่ขึ้นมานั่นแปลว่าเราสามารถสร้าง Image ของเราได้แล้ว

Run image ที่สร้างกัน

เรามาลอง Run กันดูดีกว่าว่ามันทำงานได้รึเปล่า

1
docker run --name my_test first-image

จากภาพจะเห็นว่าเราสามารถ Run ตัว image ที่เราสร้างขึ้นมาได้ ซึ่งมันเหมือน image httpd เลย ซึ่งมันเกิดมาจาก Dockerfile ของเรานั่นเอง เดี๋ยวเรามาอธิบายกัน

1
2
3
FROM httpd:2-alpine 

# FROM คือการบอกว่า เราจะสร้าง Image นี้โดยเริ่มมาจากต้นแบบที่เป็น Image ใด โดยจากตัวอย่างผมเอามาจาก httpd:2-alpine

โดยจากที่ผมอธิบายไป FROM คือบอกว่า Image ที่เราจะสร้างเนี่ยเริ่มจาก Image ไหน โดยจากตัวอย่างผมเอามาจาก httpd:2-alpine ซึ่งทำให้เวลาเราทำการ Run image ตัวนี้ทำให้ได้ผลแบบเดียวกันกับ httpd:2-alpine เลย

ลอง Customize กันหน่อย

จากนี้เราจะมาเริ่มทำการปรับแต่ง image ที่เราจะสร้างกัน โดยลองไปทำการแก้ไฟล์ Dockerfile ให้เป็นดังภาพ

1
2
3
4
5
FROM httpd:2

RUN echo "Hello wolrd"
RUN echo "YOU CAN RUN ANY COMMAND IN CONTAINER"
RUN ls -lah /

จากนั้นสั่ง build อีกครั้งหนึ่งจะได้ผลลัพธ์ดังภาพ

จะเห็นว่ามีการ run echo และ ls -lah ขึ้นมาในกระบวนการสร้างซึ่งตรงกับที่อยู่ใน Dockefile ดังนั้นก็คงจะพอเดากันได้แล้วซึ่งมันมาจากคำสั่ง RUN นั่นเอง

RUN

RUN ใน Dockerfile นั้นหมายถึงสั่ง run command ใน image ที่เราจะสร้างครับ โดยจากตัวอย่างผมสั่ง Run คำสั่ง echo และ ls -lah เพื่อโชว์เห็นว่าเราสามารถ run คำสั่งอะไรก็ได้ในเครื่อง Image (ตราบเท่าที่ Image มันมีนะ) โดยปกติ RUN ใน dockerfile มีไว้ลงพวกโปรแกรมเพิ่มเติมที่เราอยากจะ Customize หรือทำการ compile source code ตัวอย่างเช่น ผมต้องการให้ image นี้ลง vim ด้วยผมก็แค่เพิ่ม command เกี่ยวกับการ install vim เข้าไป

1
2
3
4
FROM httpd:2

RUN apt-get update
RUN apt-get install vim -y

ลองสั่ง build image ดูครับ จะเห็นว่าตอน Run จะมีการ Install ตัวโปรแกรม vim เข้าไปใน Image โดยเราสามารถลอง run image แล้ว exec เข้าไปใน image ดูจะเห็นว่าเราสามารถใช้งาน vim ได้ครับ อันนี้อยากอธิบายให้เข้าใจนิดนึงว่า คำสั่ง RUN ที่อธิบายมาทั้งหมดมันจะ Run ตอนสร้าง Image เท่านั้นนะครับ (คือทำตอนสั่ง docker build เท่านั้น) มันจะไม่ทำงานตอนสั่ง docker run นะครับ

ADD

จากคำสั่ง RUN เราสามารถ RUN คำสั่งอะไรก็ได้ใน Image ตอนสร้าง Image ได้แล้ว แต่คราวนี้เราอยากจะเอาไฟล์ไปไว้ใน Image ด้วย เพื่อให้มันทำงานบางอย่างเช่น อยากเอาไฟล์ index.html ใหม่ไปวางไว้ในตัว Image เพื่อให้เวลาเข้าหน้าแรกของ Web มีการแสดงผลแบบที่เราอยากให้เป็น แทนการที่ต้องสร้าง container แล้วค่อยเข้าไปแก้เป็นรายครั้งไป

สร้างไฟล์ index.html ไว้ใน path เดียวกับ Dockerfile โดยข้างในมีเนื้อหาประมาณ

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>MY SERVER</title>
</head>
<body>
<h1>HELLO WORLD TEST</h1>
<img src="https://live.staticflickr.com/7336/14098888813_1047e39f08.jpg" alt="">
</body>
</html>
1
2
3
4
5
6
FROM httpd:2

ADD index.html /usr/local/apache2/htdocs/index.html

# คำสั่ง ADD เป็นการบอกว่าจะเอาไฟล์จากในเครื่องที่ Run docker ที่ใดไปใส่ที่ path ใดใน image
# จากตัวอย่างคือจะเอาไฟล์ index.html ไปวางไว้ที่ path : /usr/local/apache2/htdocs/index.html ของ image

จากนั้นลองสั่ง build แล้ว Run ดูครับจะด้วยคำสั่งนี้แล้วลองเข้าผ่าน Browser จะได้ประมาณนี้ครับ

1
2

docker run --name first-image -p 80:80 first-image

โดยจากตัวอย่างผมเอาไฟล์ index.html ไปวางไว้ในเครื่อง image แทนที่ไฟล์ /usr/local/apache2/htdocs/index.html ดังนั้นเวลาเรา run image แล้วเข้าดูผ่าน Browser ทำให้เราเห็นหน้าแรกตามไฟล์ index.html ที่เราทำ

ตัดจบก่อนนะครับ

สำหรับตอนนี้เราได้ลองสร้าง Image ทดลองคำสั่ง RUN และ ADD ซึ่งเป็นคำสั่งที่ใช้งานบ่อยมากในการสร้าง Image ส่วนในตอนถัดไปมาลองคำสั่งที่ใช้งานยากขึ้นมาหน่อยคือคำสั่ง CMD , ENTRYPOINT ส่วนในตอนนี้ขอจบเพียงเท่านี้ละกันครับ

โปรโมท Page

ผมทำ Page บน Facebook แล้วนะครับ ใครสนใจรับการแจ้งเตือนเวลาผมอัพเดท Blog ก็ไปกดติดตามกันได้ครับ กดตามลิ้งไปได้เลย Facebook page

เพลงประกอบการเขียน Blog

เพลงประกอบ Series ญี่ปุ่นเรื่อง Galileo โดยนักแสดงนำร้องเอง ซึ่งพอมาอ่านทีหลังพึ่งรู้ว่า นักแสดงนำทั้งคู่นี่ตัว TOP ของญี่ปุ่นเลย