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
// สร้าง 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
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 ของญี่ปุ่นเลย

Docker Part 4 - Inside Docker

ลองเข้าไปใน Container

จากตอนก่อนๆเราสร้างตัว Container ขึ้นมามากมายหลายตัว คราวนี้หลายคนคงเกิดข้อสงสัยขึ้นมาว่า เฮ้ยจะเข้าไปดูในเครื่อง Container ยังไง เป็นไปได้ไหม ซึ่งคำตอบก็คือได้ เรามาลองกันเลยครับ

EXEC เข้าเครื่องกัน

1
2
3
4
5
6
7
8
9
10
# สร้างเครื่อง docker
docker run -d --name my-httpd -p 80:80 httpd

# ลองเข้าเครื่อง container ด้วยคำสั่งนี้

docker exec -ti my-httpd bash

# exec เป็นคำสั่งที่สั่งให้ run คำสั่งที่อยู่ใน container จากตัวอย่างคือสั่งใช้งานคำสั่ง bash
# -t อันนี้เป็น options ที่ทำการสร้าง tty มองว่าทำให้เราสามารถพิมพ์และแสดงผลแบบ shell ได้
# -i อันนี้เป็น options ให้ทำให้เราเปิด std in ค้างไว้รอ แม้จะไม่ส่งอะไรเข้าไปก็ตาม

ซึ่งพอ run คำสั่งด้านบนแล้วเราจะเข้าใน container นั้นแล้วดังภาพ

คราวนี้ลองกดคำสั่งต่างๆใน container ได้เลยครับ เช่น ls , clear และอื่นๆ จะเห็นว่าคำสั่งพื้นฐานของ Linux ได้ คราวนี้ลองคำสั่งพวก vi ดูครับ ซึ่งพอลองแล้วจะเห็นว่าใช้งานไม่ได้ครับ หรือลองพิมพ์คำสั่ง java ดูก็จะพบว่าใช้งานไม่ได้

สร้าง container ขึ้นมาอีกตัว

1
2
3
4
5
6
7
8
9
10
11

docker run -ti --name maven maven bash

# bash ตัวท้ายสุดในคำสั่ง docker run มีควาหมมายว่า เราจะ run คำสั่งอะไรใน container นี้ โดยจากตัวอย่างเราสั่งใช้งานคำสั่ง bash
# จริงๆตรงนี้ไม่ได้หมายความว่าส่งคำสั่งเข้าไปอย่างเดียวนะครับ มันมีเงื่อนไขของมันเดี่ยวอธิบายในบทที่เราสร้าง image กัน

docker exec -ti maven bash

# ลองพิพม์

java

จากการทดลองด้านบนเราจะเห็นว่ากับ container นี้เราสามารถใช้คำสั่ง java ได้ จากการทดลองนี้ทำให้เราทราบแล้วว่า container แต่ละตัวนั้นมีคำสั่งไม่เท่ากัน อันนี้เป็นผลมาจากเขาสร้าง image เพื่อใช้ในงานต่างกัน เช่น image httpd นั้นเป็น apache ก็ไม่จำเป็นต้องลง java เช่นกัน maven มีไว้ compile java ก็ไม่จำเป็นต้องมี httpd คำสั่งอย่างพวก vi ก็เช่นกันว่าไม่ต้องมีเพราะ container มีไว้ application ไม่มีใครเข้าไปใช้งาน vi อยู่แล้ว

ทำไมต้องลองเข้าไปใน container

จริงๆเราไม่ต้องเข้าไปใน container เลยก็ได้ครับ แต่เพื่อความเข้าใจ Image ที่เขาแจกว่าข้างในมีอะไรบ้าง หรืออยากไปดู config ของเครื่อง หรือตรวจสอบ image ของเราว่าสร้างได้ผลลัพธ์อย่างที่เราต้องการ แต่ที่ใช้งานบ่อยจริงๆคือเข้าไปดู config เช่น เข้าไปดู config ของ httpd ว่าเขาวางไว้ตรงไหน ตั้งค่าไว้ยังไงเป็นต้น เพราะเวลาเราต้องการเอา image ของเขาไป customize ต่อจะได้รู้ว่าต้อง customize ตรงไหน

แก้ไขไฟล์ใน container ไม่ได้ทำยังไง

ลงคำสั่งใน Container สิครับ

ก็อย่างที่บอกไปครับว่า container มันก็คือเครื่องจำลองนั่นแหละครับ ถ้าอยากมี vi ก็ install vi มันเลยสิ แต่การลงก็อาจจะยากหน่อยนะครับ เพราะเราต้องทราบด้วยว่า container ทีเราใช้อยู่เนี่ยเป็น distro ไหนของ linux เพราะแต่ละ distro นั้นลงไม่เหมือนกัน ถ้าเป็น ubuntu ก็ใช้ apt-get install vi แต่ถ้าเป็น CentOS จะต้องใช้ yum install เป็นต้น

1
2
3
4
5
6
7
8
9
10
11
12
# ลง vim บน httpd
# ขอ ลง vim แทนละกัน ลง vi ไม่ได้

docker exec -ti my-httpd bash
apt-get update
apt-get install vim

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

# path : /usr/local/apache2/htdocs/ เป็น path ที่ image httpd นี้ใช้เป็นที่เก็บไฟล์ที่จะให้บริการกับ client
# เวลาเรียกใช้งาน httpd โดยเราจะแก้ไฟล์ index.html ให้เป็นอย่างอื่น เพื่อตรวจสอบดูว่าเวลาแก้ไขไฟล์นี้แล้ว
# เวลาเรียกใช้งานมันจะเปลี่ยนไหม

เปิดมาได้หน้าตาแบบนี้

ทำการแก้เป็นแบบนี้

ลองเข้าผ่าน web browser ดู (ตอนผมสั่ง สร้าง docker ตัวนี้ผม bind port 80 ไว้กับเครื่องที่ลง docker เครื่องที่ผมลง docker อยู่ที่ 192.168.56.101)

เอาไฟล์จาก container ออกมาแก้ไข แล้วค่อยส่งกลับเข้าไป

จากวิธีการ Install คำสั่งในเครื่อง container ดูยุ่งยากพอสมควร docker เลยมีช่องทางให้เราสามารถเอาไฟล์จาก container ออกมาได้ และ ทำให้สามารถส่งกลับไปได้ด้วย ซึ่งใช้คำสั่ง

1
2
3
4
5
6
7
# เอาไฟล์ index.html ออกมาจาก container
docker cp my-httpd:/usr/local/apache2/htdocs/index.html .

# docker cp เป็นคำสั่ง copy file จาก source ไปหา destiantion ว่าง่ายๆ เหมือน cp เลยแหละ โดยจะเอาเข้าหรือเอาออกจาก
# container ให้ดูที่ : อยู่กับตัวไหนตัวนั้นหมายถึง container โดยจากตัวอย่างข้างบนหมายถึง
# เอาไฟล์ที่ path : /usr/local/apache2/htdocs/index.html
# บน container my-httpd ออกมาไว้ที่ current directory ของเครื่องที่ลง Docker

จากนั้นลองเอาไฟล์ออกมาแก้ไขไฟล์ แล้วใส่กลับเข้าไปด้วยคำสั่ง

1
2
3
docker cp /your/file/path/index.html  my-httpd:/usr/local/apache2/htdocs/index.html 

# คำสั่งนี้เป็นการ copy file จากเครื่องที่ลง docker ไปวางที่ /usr/local/apache2/htdocs/index.html ของ container my-httpd

จากนั้นลอง Test กับ Browser

Mount ไม่ได้เหรอ

จริงๆมีอีกวิธีที่ทำได้นะครับคือการใช้ Mount volume ( -v ตอนสั่ง docker run) แต่วิธีนี้จะใช้ไม่ได้เมื่อเรา mount volume เข้าไปใน path ที่มี file อยู่แล้วของ container ซึ่งเมื่อทำแบบนั้นข้อมูลใน path นั้นจะหายไป ตัวอย่างเช่น

1
docker run --name no_mount_httpd -p 80:80 httpd

จากนั้นลองเข้าดูผ่าน Browser จะได้แบบนี้

จากนั้นทำการ stop no_mount_httpd จากนั้นสั่งคำสั่งต่อไปนี้

1
2
3
4
mkdir -p /root/share_docker/test_mount_httpd
docker run --name mount_httpd -p 80:80 -v /root/share_docker/test_mount_httpd:/usr/local/apache2/htdocs/ httpd

# Mount ไปที่ /usr/local/apache2/htdocs/ เพราะ image httpd เขากำหนดให้ path นี้เป็น path จะไว้เก็บไฟล์ไว้ให้ Httpd serve ไฟล์ให้ client ใช้งาน

จากน้ันทำการเปิด Browser

จะเห็นว่ามีความเปลี่ยนแปลงซึ่งเมื่อเราลอง exec เข้าไปในเครื่องจะเห็นว่าไฟล์ index.html หายเพราะว่าเราทำการ mount /root/share_docker/test_mount_httpd ซึ่งเป็น Directory เปล่าๆเข้าไป map กับ path /usr/local/apache2/htdocs/ จึงทำให้ไฟล์ index.html หายไป จากการทดลองนี้ทำให้เราทราบว่า “เมื่อเรา mount volume อะไรเข้าไปใน container ตอนสั่ง docker run มันจะยึดข้อมูลที่อยู่บนเครื่องที่ลง Docker เป็นหลัก” อย่างในตัวอย่างมันจะยึดข้อมูลที่อยู่ใน /root/share_docker/test_mount_httpd ซึ่งไม่มีข้อมูลอะไรเลยเป็นหลัก

Container ถ้าลบแล้วก็หายไปเลย

ขอให้พึงระลึกไว้เสมอว่า Container ก็เหมือนเครื่องจำลองขึ้นมา เมื่อใดก็ตามที่เราสั่ง docker rm สั่งลบ Container นั้นไป ข้อมูลทั้งหมดที่อยู่ใน Container นั้นจะหายไป (ยกเว้นส่วนที่เราทำการ Mount volume ไว้) ดังนั้นเมื่อคุณแก้อะไรใน Container หรือเพิ่มอะไรเข้าไปใน Container เมื่อคุณลบมันแล้วสร้างมาใหม่ Container ที่สร้างมาใหม่จะไม่มีอะไรที่คุณแก้ไขไว้ใน Container เดิมนะครับ

สรุป

ในตอนนี้เรามาเริ่มเข้าไปใน Container เพื่อดูว่าในเครื่อง container นั้นมีอะไรอยู่บ้าง แล้วเราสามารถแก้ไขอะไรใน Container ได้บ้าง แก้ไขได้ด้วยวิธีใดบ้าง ข้อจำกัดบางประการของการแก้ไข ส่วนตอนต่อไปขอคิดก่อนละกันว่าจะทำ Docker Image หรือลองใช้ Docker compose ดี

โปรโมท Page

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

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

เช่นเดียวกับตอนที่แล้วอาจจะมีคนสงสัยว่า PIN กับ MARY คือใคร กดตามลิ้งค์ไปได้เลย น้องน่ารักมากๆครับ

Pin

Pin

Mary

Mary

Docker Part 3 - Use Docker

ใช้งาน Docker กับการพัฒนา Application

จากตอนที่แล้วได้ทดลองใช้ Docker เบื้องต้นไม่ว่าจะเป็นการสร้าง การเปิด Bind port ตอนนี้เราจะมาใช้ Docker กับการพัฒนา Application ให้มากขึ้นเพื่อให้เห็นภาพว่า Docker มีประโยชน์อะไรในการพัฒนา ซึ่งหลายๆคนตอนแรกที่อ่านเรื่อง Docker แล้วมีการบรรยายสรรพคุณต่างๆนาๆก็จะไม่ค่อยเห็นภาพ ตอนนี้เราจะค่อยๆเห็นภาพนั้นกันครับ

ใช้ Docker สร้าง Database กัน

1
2
3
docker run --name my_db -e MYSQL_ROOT_PASSWORD=root -d -p 3306:3306  mysql:5.7

# -e คือการตั้งค่า ENVIRONMENT ให้กับ Docker ตัวนั้น โดยในตัวอย่างเป็นการตั้งค่า ENVIRONMENT ชื่อ MYSQL_ROOT_PASSWORD ให้มีค่าเท่ากับ root ในส่วนนี้จะอธิบายต่อในภายหลัง

เมื่อ Run คำสั่งลองใช้ Tools ที่ใช้ในการเข้าใช้งาน Database เข้าไปใช้งานตัว Database กัน โดยของผมจะใช้เป็นตัว mysql query browser โดย user pass ของ docker ตัวนี้คือ

1
2
username : root
password : root

ส่วน Host ก็ตามเครื่องที่ท่านลง Docker ไว้เลยครับ โดยของผมจะเป็น 192.168.56.101 หลัง Login เข้าไปใช้งานแล้วจะได้ดังภาพ

จะเห็นว่าเราสามารถสร้าง Database server มาใช้งานง่ายเพียงแค่ 1 คำสั่งของ Docker ซึ่งง่ายกว่าที่เราต้อง Download ตัว Database server มาลงที่เครื่องเอง ซึ่งบางทีเราพัฒนาหลาย Application แต่ละตัวใช้ database version ไม่เหมือนกันเช่น บาง App ใช้ Database : MYSQL5.7 บางตัวใช้ version 8 ซึ่งเป็นการยากมากที่เราจะลง Database server หลายๆ version ในเครื่องเดียว

Environment

ในส่วนตอนสร้าง container mysql นั้นจะมีการใส่ Parameter -e ที่ผมบอกว่าคือ Environment ไอตัว Environment มันคือค่าที่ถูก Set ให้กับตัว OS ครับ ซึ่งพอเรา Set แล้วตัว Environment ไปแล้ว ตัว Application สามารถดึงค่า Environment มาใช้งานได้ครับ

เราพักเรื่อง Environment แล้วมาดูที่ Image กัน จะเห็นว่าตัวเราสามารถเอาตัว Image mysql มาสร้างเป็น Container ใช่ไหมครับ ตอนแรกมันก็ไม่ค่อยมีอะไร ต่อมาก็เริ่มมีคำถามขึ้นมาว่า อยากได้ password ของ mysql ที่ไม่ใช่ default อะ อยากตั้งเป็น password อื่น จะทำยังไง

ถ้าเรามองว่า Image คือ Function ที่เอาไว้สร้างเครื่องตามที่เรากำหนดไว้ ถ้าเราอยากให้สร้างเครื่องที่ลง Mysql ที่มี password ไม่เหมือนกัน เราก็แค่สร้าง function parameter ขึ้นมาเพื่อส่ง password ทีแตกต่างเข้าไป ดังนั้น function parameter ที่เราจะ set ให้เครื่องก็คือ Environment นั่นเอง ตัวอย่าง mysql มี Environment ชื่อ MYSQL_ROOT_PASSWORD เพื่อกำหนด password ให้ user root ดังนั้นถ้าอยาก Customize อะไรบางอย่างของ Image ลองดูที่ Page ของคนที่ทำ Image นะครับว่าเขามี ENVIRONMENT อะไรให้เรา Customize ได้บ้าง อย่างเช่น Mysql และเช่นกัน เมื่อคุณทำการสร้าง Image ก็ควรทำให้มันสามารถ Customize ได้ เช่น Endpoint ของ service ที่จะติดต่อ Endpoint ของ Database เป็นต้น

Dev บน Docker กัน

ปัญหาที่เรามักเจอกันบ่อยๆเวลา Dev คือ เราไม่ได้ Dev บนเครื่องจริงครับ ตัวอย่างเช่น เรา Dev บน PHP Version 5.3 แต่ PROD เป็น Version 5.7 หรือ Enable module ต่างๆไม่เท่ากันก็มักจะเกิดเหตุการณ์เช่น Run บน Dev ได้ แต่ Run บน PROD ไม่ได้ เนื่องจากปัญหาเหล่านี้เลยมีคนคิดวิธีแก้ปัญหามาหลายอย่าง Docker ก็เป็นหนึ่งในนั้นคือ ทำการสร้าง Image แล้วก็ นำ Image นั้นไป Deploy เป็น Container แล้ว Run ที่ไหนก็ได้ที่ลง Docker ดังนั้นมันก็เป็นการการันตีว่า Application ของเรา Run ได้แน่นอน เพราะเรา Dev ด้วยเครื่องจำลองที่เรากำหนด ดังนั้น Dev กับ Prod ย่อมเหมือนกัน

สร้าง Directory ขึ้นมาสักที่นึงในเครื่องที่ลง Docker ของคุณครับ ของผมสร้างที่ /root/share_docker/dev_docker

1
2
3
4
5
6
7
8
9
docker run --name dev_docker -d -p 80:80  -v /path/to/directory:/var/www/html/ php:5.6-apache

# ของผมจะเป็น

docker run --name dev_docker -d -p 80:80 -v /root/share_docker/dev_docker:/var/www/html/ php:5.6-apache

# -v คือการ Mount volume ของเครื่องที่ลง docker ให้ไปโผล่บนเครื่องจำลองที่เราสร้างขึ้นมา คล้ายๆทำ share drive กันครับ โดย
# หน้า : คือ Path ในเครื่องที่ลง Docker ที่เราอยากจะให้ไปโผล่ใน Container ของผมคือ /root/share_docker/dev_docker
# หลัง : คือ Path ของเครื่องจำลอง ( container ) ที่เราจะ map เข้ากับ path ที่อยู่หน้า :

จากนั้นลองสร้างไฟล์ index.php ใน directory ที่เรา map เข้ากับ container ครับ จากนั้นในไฟล์เขียน code ประมาณนี้ลงไป

1
2
3
<?php 
echo "HELLO WORLD NICO";
?>

จากนั้นเข้า Browser ใส่ IP เครื่องที่ลง Docker ครับ (ของผมคือ 192.168.56.101) จะได้ดังภาพครับ

จากนั้นลองแก้ไขไฟล์ตัว Index.php เป็นอย่างอื่นตามใจครับ โดยของผมจะแก้เป็น

1
2
3
<?php 
echo "HELLO WORLD Kawjao MeltMallow";
?>

เมื่อเปิดบน Browser ใหม่ก็จะเห็นว่าข้อความเปลี่ยนไปดังภาพ

ด้วยวิธีการนี้เราสามารถพัฒนา Application ด้วยภาษา PHP โดยไม่ต้องลง PHP ที่เครื่องเลย อยากเขียน code อะไรก็แก้ใน Directory ที่เรา Mount volume เข้าไปใน Container

สรุป

ในตอนนี้เราได้เห็นวิธีการเอา Docker มาทำเป็น Database server เอา Docker มาใช้ในการพัฒนา PHP โดยที่เราไม่ต้องลง PHP ที่เครื่องเลย ซึ่งน่าจะเห็นภาพการเอา Docker มาใช้งานกับการ Develop Appllication และในตอนนี้ได้สอนการใช้งาน Environment และการ Mount volume ซึ่งต่อจากนี้เราจะเริ่มใช้ทั้งสองอย่างนี้บ่อยขึ้น ดังนั้นไปลองใช้งานให้คล่องนะครับ เช่น environment ของ mysql ก็ลองเปลี่ยน MYSQL_ROOT_PASSWORD เป็นค่าอื่นดู แล้วลอง Login เข้า mysql ดูว่ามันใช้ password ตามที่เราตั้งไหม หรือ Mount volume ก็ลองเปลียนตำแหน่ง Mount ดูว่าถ้าเปลี่ยนแล้วมันไปโผล่ใน Docker ไหม

โปรโมท Page

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

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

สำหรับใครที่สงสัยว่า Nico กับ Kawjao MeltMallow คือใคร กดตามลิ้งรูปด้านล่างได้เลย

Nico

Nico

Kawjao

Kawjao