การพัฒนา Microservice บน Docker Container สำหรับผู้เริ่มต้น
บทความโดย ผศ.ดร.ณัฐโชติ พรหมฤทธิ์
ภาควิชาคอมพิวเตอร์
คณะวิทยาศาสตร์
มหาวิทยาลัยศิลปากร
บทความนี้เราจะทำความเข้าใจแนวคิดของ Microservice ซึ่งเป็นสถาปัตยกรรมการพัฒนา Software ด้วยการแตกระบบออกเป็นส่วนย่อยที่มีหน้าที่การทำงานเพียงเรื่องเดียว ซึ่งในปัจจุบันบริษัทชั้นนำหลายบริษัทได้นำสถาปัตยกรรมแบบนี้ไปใช้งานจริงๆ เป็น Production เช่น Amazon, Netflix และ Wongnai ฯลฯ
โดยตั้งแต่ตอนที่ 3 ของบทความ เราจะได้ทดลองสร้าง Microservice เพื่อจำลองงานทะเบียนนักศึกษา ที่มีการแลกเปลี่ยนข้อมูลแบบ RESTful API และ RPC (Remote Procedure Call) ซึ่งจะมีการ Deploy บน Docker Container ครับ
หมายเหตุ
- บทความนี้มีการใช้งาน VPS และ Let’s Encrypt ตามที่ได้ Config จาก วิธีติดตั้ง VPS และ Let’s Encrypt ด้วย Docker Container แบบง่ายๆ
- คำสั่ง Stop/Delete Container และ Delete Image ด้วย Docker-compose คือ
docker-compose down --rmi all
1.Monolithic
หลายคนน่าจะรู้จักสถาปัตยกรรมแบบ Three-tier Application Architecture ซึ่งมีการแบ่งชิ้นส่วน (Component) ของ Software เป็น 3 Layer ได้แก่ Presentation Layer, Application Layer และ Data Layer
การพัฒนาซอฟต์แวร์ภายใต้โครงสร้างของสถาปัตยกรรมแบบนี้ บริษัทมักแบ่งการทำงานของ Layer ตามความถนัดของแผนกหรือทีม ซึ่งแต่ละทีมจะมีความเป็น Specialists สูงในงานเฉพาะด้าน โดยซอฟต์แวร์ที่พัฒนาจะแบ่งออกเป็น Module ที่ใช้ภาษาเดียวกัน ฐานข้อมูลเดียวกัน และรันใน Process เดียวกัน สถาปัตยกรรมของ Software แบบนี้เรียกว่า Monolithic
สมมติว่าบริษัทแห่งหนึ่งมีนักพัฒนา 10 คน แบ่งทีมพัฒนา Software "ระบบทะเบียนนักศึกษา" เป็น 2 ทีม ทีมละ 5 คน ทีมที่ 1 รับผิดชอบ Student Module ทีมที่ 2 รับผิดชอบ Enroll Module ในการพัฒนาแบบ Monolithic ทั้งสองทีมจะต้องเลือกใช้เทคโนโลยีเดียวกัน และ Deploy Software ที่รวมทุก Function ไว้ใน Process เดียว ดังภาพด้านล่างซ้าย การปรับขนาดเพื่อรองรับ Load ของ Monolithic จึงต้อง Replicate ไปทั้งก้อน
แต่หากมี Module หนึ่งพัง อาจส่งผลกระทบกับการทำงานในส่วนอื่นของระบบ ยิ่งการ Deploy เป็นก้อนใหญ่มาก ความเสี่ยงต่อระบบโดยรวมก็จะยิ่งมากขึ้น
2.Microservices
เมื่อเราแตกระบบออกเป็นส่วนย่อยที่มีหน้าที่การทำงานเพียงเรื่องเดียวอย่างชัดเจน ดังภาพล่างขวามือ หากมีบาง Service พัง มันจะกระทบกับ Service อื่นน้อยกว่า
นอกจากนี้แต่ละทีมจะสามารถทำงานเสร็จโดยไม่ต้องรอทีมอื่นทำงานบางอย่างก่อน ทำให้แต่ละทีมสามารถพัฒนา ทดสอบ และ Deploy Software ได้อย่างอิสระ โดยเลือกใช้เทคโนโลยีที่แตกต่างกัน (รวมทั้ง Database) ตามความเหมาะสม
โครงสร้างของสถาปัตยกรรมแบบนี้มีผลต่อทีมพัฒนา ซึ่งสมาชิกในทีมบางคนจะต้องเป็นคนเขียน Code ในส่วนของ Font-End, Back-End และ Database รวมทั้งทำ Unit Test, Code Review, Deploy และคอยดู Alert จากระบบ แล้วรีบแก้ไขปัญหา ถ้าพบ Bug ขณะ Deploy อาจต้อง Rollback กลับไปยัง Version ก่อนหน้า
ลักษณะของทีมแบบนี้จะต้องเป็นทีมขนาดเล็ก แต่มีจำนวนคนเพียงพอที่จะช่วยกันดูแล Service ที่ตัวเองสร้างขึ้นมา ("Operating What You Build")
เมื่อ Service มีขนาดเล็ก เราจึงสามารถ Release Software ได้ถี่ขึ้น เป็นวันละหลายสิบครั้ง จากเดิมที่อาจมีการ Release ทุก 3 เดือน 6 เดือน ซึ่งต้องใช้กระบวนการรวบรวม และ Deploy Software แบบอัตโนมัติ (CI/CD)
การแบ่งงานเป็น Service ขนาดเล็ก ทำให้การปรับขนาดเพื่อรองรับ Load ทำได้ง่ายขึ้น เพียงเลือกขยายเฉพาะ Service ที่ถูกใช้งานเยอะๆ
ส่วนย่อยๆ ของ Software แบบนี้เรียกว่า Microservice ซึ่งเป็นแนวคิดที่ได้รับความนิยมเพิ่มขึ้นในปัจจุบัน โดยกระบวนการทาง DevOps ที่อาศัยเทคโนโลยีของ Container จะเป็นเครื่องมืออันทรงพลังที่จะทำให้ Microservice ประสบความสำเร็จครับ
อย่างไรก็ตามสิ่งที่ยากที่สุดของการพัฒนา Software แบบ Microservice Architecture คือ การออกแบบ เพราะถ้าเราออกแบบไม่ดีก็จะเพิ่มความซับซ้อนให้กับระบบโดยไม่จำเป็น เช่นกรณีที่เราแตกระบบออกเป็นส่วนย่อยจนกระทั่งเป็น Function แล้วแยกกัน Deploy บน Container เมื่อรวมเป็นระบบแล้วอาจจะทำให้เกิดการเรียกใช้ Service จำนวนมาก จนทำให้ระบบทำงานช้า และสิ้นเปลือง Network Bandwidth
อีกประเด็นที่ต้องระมัดระวัง คือ การเรียก Service ข้าม Bounded Context หรือข้ามขอบเขตของ Domain ซึ่งจะเกิดขึ้นเมื่อเรายึดหลักการ Don’t Repeat Yourself (DRY) โดยการแตกระบบออกเป็นส่วนย่อยที่ทำหน้าที่เฉพาะทางเพื่อให้เกิดการ Reuse มากจนเกินไป
ดังนั้นในการออกแบบ Microservice ที่ดี เราจะยึดหลัก 2 ประการ คือ
Loosely Coupling แต่ละ Service มีหน้าที่ทำงานเพียงอย่างเดียว และควรขึ้นกับ Service อื่นให้น้อยที่สุด
High Cohesion ภายในแต่ละ Service จะมีงานที่ร่วมกันทำตามเป้าหมาย
เพื่อให้เห็นภาพมากขึ้น เราจะยกตัวอย่าง Microservice จำลองระบบงานทะเบียนนักศึกษาที่มี Service ทั้งหมด 3 Service ได้แก่
- Student เป็น Service ที่เก็บข้อมูลนักศึกษา
- Enroll เป็น Service ที่เก็บข้อมูลการลงทะเบียน โดยสมมติว่าเป็นการลงทะเบียนแบบเหมาจ่าย
- Email เป็น Service ที่ส่ง Email ไปยังนักศึกษาเพื่อแจ้งว่าได้ลงทะเบียนแล้ว
เมื่อนักศึกษาลงทะเบียน จะมี Flow เกิดขึ้น ดังนี้
- สร้าง Record ใหม่ ที่ Student Service
- สร้าง Record ลงทะเบียนใหม่ ที่ Enroll Service
- ส่ง Email ไปยังนักศึกษาว่าได้ลงทะเบียนเรียบร้อยแล้ว
เราอาจออกแบบให้ Student Service เรียกใช้ Enroll และ Email Service ผ่าน RESTful API ซึ่งเมื่อมี Request เข้ามา มันจะต้องรอ Response จาก Enroll และ Email Service ก่อนที่ Student จะ Reply กลับไปยังผู้ใช้งาน โดยเราเรียกการสื่อสารแบบ Request/Reply แบบนี้ว่า Synchronous Communication
การออกแบบสถาปัตยกรรม Software แบบนี้จะทำให้เกิด High Coupling เพราะว่า Service ของเราผูกกันแน่น โดย Student Service จะทำงานเกินหน้าที่ของตัวเอง
จากเดิมที่ Student Service จะต้องทำงานหลายหน้าที่ เราจะสร้าง register_gateway เพื่อควบคุม Flow ของงานแทน โดยเรายอมให้เกิด High Coupling ที่ register_gateway นอกจากนี้เราจะใช้ RPC ในการสื่อสารกับ Student Service แบบ Synchronous (ต้องรอ Student ID จาก Student Service) แทนการใช้ RESTful API ขณะที่เราจะใช้ RPC ในการสื่อสารกับ Enroll Service และ Email Service แบบ Asynchronous เพราะสามารถทำทั้ง 2 งานขนานกันไปได้ โดย register_gateway ไม่จำเป็นต้องรอให้ Service ทั้ง 2 ตัว ทำงานเสร็จก่อนจึงจะ Reply กลับไปยังผู้ใช้
3.RabbitMQ
ก่อนจะสร้าง Register Gateway เพื่อเป็นหน้าด่านรับ Request และประสานงานกับ Service ต่างๆ เราจะต้องติดตั้ง RabbitMQ ให้ทำหน้าที่เป็นท่อส่งข้อมูล เมื่อ Service ต่างๆ สื่อสารกันผ่าน RPC โดยจะมีการ Config ตามขั้นตอนดังต่อไปนี้
- Remote Login ไปยัง Cloud Server โดยใช้ ssh
ssh [email protected]
- สร้าง Project ชื่อ mq_dock ภายใน Folder มีไฟล์ docker-compose.yml
.
|__ docker-compose.yml
- แก้ไข docker-compose.yml เพื่อติดตั้ง RabbitMQ Container ตามตัวอย่างด้านล่าง
version: "2"
services:
rabbitmq:
image: "rabbitmq:management"
container_name: rabbitmq
restart: always
networks:
default:
external:
name: microservice_network
- สร้าง Bridge network โดยตั้งชื่อเป็น microservice_network
docker network create microservice_network
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
4.Register Gateway
ทีนี้เราจะสร้าง Register Gateway เพื่อประสานงานกับ Service ต่างๆ รวมทั้งการรับ Request จากผู้ใช้ผ่าน RESTful API ตามขั้นตอนดังนี้
- สร้าง Project ใหม่ ภายใน Folder register_gateway_dock ประกอบด้วย ไฟล์ และ Folder ดังต่อไปนี้
.
|__ docker-compose.yml
|__ python/
|__ Dockerfile
|__ requirements.txt
|__ api.py
- แก้ไข docker-compose.yml ตามตัวอย่างด้านล่าง
version: '3'
services:
register:
container_name: register_gateway
build: python/
restart: always
ports:
- "7001:80"
networks:
default:
external:
name: microservice_network
- แก้ไข Dockerfile ตามตัวอย่างด้านล่าง
FROM python:3.7.3-alpine3.8
RUN apk add --no-cache build-base
WORKDIR /app
COPY api.py .
COPY requirements.txt .
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
CMD uvicorn api:app --host 0.0.0.0 --port 80
- แก้ไขไฟล์ requirements.txt เพื่อติดตั้ง Libray ที่จำเป็น
เราจะใช้ fastapi และ uvicorn สำหรับสร้าง RESTful API โดยนิยามข้อมูลที่รับเข้าแบบ JSON ด้วย pydantic และใช้ nameko เพื่อเรียก Service ต่างๆ ด้วย RPC
fastapi
uvicorn
pydantic
nameko==2.10.0
- แก้ไขไฟล์ api.py
from fastapi import FastAPI
from pydantic import BaseModel
from nameko.rpc import rpc
from nameko.standalone.rpc import ClusterRpcProxy
class Student(BaseModel):
firstname:str
lastname:str
email:str
app = FastAPI()
broker_cfg = {'AMQP_URI': "amqp://guest:guest@rabbitmq"}
@app.post("/register/")
def api(student_item: Student):
with ClusterRpcProxy(broker_cfg) as rpc:
sid =rpc.student.insert(student_item.firstname, student_item.lastname, student_item.email)
rpc.enroll.insert.call_async(sid, student_item.firstname, student_item.lastname)
rpc.email.send.call_async(sid, student_item.firstname, student_item.lastname, student_item.email)
print(sid)
return {'results': 'registered'}
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
5.Student Service
Student เป็น Service ที่เก็บข้อมูลนักศึกษา เมื่อถูกเรียกใช้ผ่าน RPC มันจะสร้าง Record ใหม่ ในฐานข้อมูลของตัวเอง โดยเราจะ Config docker-compose และไฟล์อื่นๆ ตามขั้นตอนต่อไปนี้
- สร้าง Project ใหม่ ภายใน Folder student_dock ซึ่งประกอบด้วย ไฟล์และ Folder ดังต่อไปนี้
.
|__ docker-compose.yml
|__ mariadb/
| |__ data/
| |__ initdb/
| | |__ devops_db.sql
| |__ backup/
|__ python/
|__ Dockerfile
|__ requirements.txt
|__ rpc.py
- แก้ไข docker-compose.yml ตามตัวอย่างด้านล่าง
version: '3'
services:
student_rpc:
container_name: student_rpc
build: python/
restart: always
depends_on:
- db
networks:
- microservice
- default
db:
container_name: mariadb
image: mariadb:latest
restart: always
volumes:
- ./mariadb/initdb/:/docker-entrypoint-initdb.d
- ./mariadb/data/:/var/lib/mysql/
environment:
- MYSQL_ROOT_PASSWORD=devops101
- MYSQL_DATABASE=devops_db
- MYSQL_USER=devops
- MYSQL_PASSWORD=devops101
pma:
container_name: student_phpmyadmin
image: phpmyadmin/phpmyadmin
restart: always
networks:
- webproxy
- default
environment:
VIRTUAL_HOST: mydb.lab20.cpsudevops.com
LETSENCRYPT_HOST: mydb.lab20.cpsudevops.com
expose:
- "80"
networks:
default:
external:
name: student_network
microservice:
external:
name: microservice_network
webproxy:
external:
name: webproxy
- แก้ไข Dockerfile ตามตัวอย่าง
FROM python:3.7.3-alpine3.8
RUN apk add --no-cache mariadb-dev build-base
WORKDIR /app
COPY rpc.py .
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
CMD nameko run rpc --broker amqp://guest:guest@rabbitmq:5672
- แก้ไขไฟล์ requirements.txt
mysqlclient
nameko==2.10.0
- แก้ไขไฟล์ rpc.py
import MySQLdb
from nameko.rpc import rpc
def connect():
DBconnect = MySQLdb.connect(host='db',
user='devops',
passwd='devops101',
db='devops_db',
port=3306)
return DBconnect
def insert(firstname, lastname, email):
DBconnect = connect()
cur = DBconnect.cursor()
cur.execute("INSERT INTO student (FirstName, LaseName, Email) VALUES (%s, %s, %s);", (firstname, lastname, email))
id = cur.lastrowid
DBconnect.commit()
DBconnect.close()
return id
class Service:
name = "student"
@rpc
def insert(self, firstname, lastname, email):
result = insert(firstname, lastname, email)
return result
- แก้ไขไฟล์ devops_db.sql
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";
CREATE TABLE `student` (
`id` int NOT NULL,
`FirstName` varchar(255) NOT NULL,
`LaseName` varchar(255) DEFAULT NULL,
`Email` varchar(255) NOT NULL
) ENGINE=InnoDB CHARSET=utf8 COLLATE utf8_general_ci;
ALTER TABLE `student`
ADD PRIMARY KEY (`id`);
ALTER TABLE `student`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;
- สร้าง Bridge network โดยตั้งชื่อเป็น student_network
docker network create student_network
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
6.Enroll Service
Enroll เป็น Service ที่เก็บข้อมูลการลงทะเบียน เมื่อถูกเรียกใช้งานผ่าน RPC มันจะสร้าง Record ใหม่ ที่ประกอบด้วยวิชาต่างๆ ใน 1 ภาคการศึกษา ในฐานข้อมูลของตัวเอง โดยเราจะ Config docker-compose และไฟล์อื่นๆ ตามขั้นตอนต่อไปนี้
- สร้าง Project ใหม่ ภายใน Folder enroll_dock ประกอบด้วย ไฟล์ และ Folder ดังต่อไปนี้
.
|__ docker-compose.yml
|__ mariadb/
| |__ data/
| |__ initdb/
| | |__ devops_db.sql
| |__ backup/
|__ python/
|__ Dockerfile
|__ requirements.txt
|__ rpc.py
- แก้ไข docker-compose.yml ตามตัวอย่างด้านล่าง
version: '3'
services:
enroll_rpc:
container_name: enroll_rpc
build: python/
restart: always
depends_on:
- db
networks:
- microservice
- default
db:
container_name: enroll_mariadb
image: mariadb:latest
restart: always
volumes:
- ./mariadb/initdb/:/docker-entrypoint-initdb.d
- ./mariadb/data/:/var/lib/mysql/
environment:
- MYSQL_ROOT_PASSWORD=devops101
- MYSQL_DATABASE=devops_db
- MYSQL_USER=devops
- MYSQL_PASSWORD=devops101
pma:
container_name: enroll_phpmyadmin
image: phpmyadmin/phpmyadmin
restart: always
networks:
- webproxy
- default
environment:
VIRTUAL_HOST: mydb2.lab20.cpsudevops.com
LETSENCRYPT_HOST: mydb2.lab20.cpsudevops.com
expose:
- "80"
networks:
default:
external:
name: enroll_network
microservice:
external:
name: microservice_network
webproxy:
external:
name: webproxy
- แก้ไข Dockerfile ตามตัวอย่าง
FROM python:3.7.3-alpine3.8
RUN apk add --no-cache mariadb-dev build-base
WORKDIR /app
COPY rpc.py .
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
CMD nameko run rpc --broker amqp://guest:guest@rabbitmq:5672
- แก้ไขไฟล์ requirements.txt
mysqlclient
nameko==2.10.0
- แก้ไขไฟล์ rpc.py
import MySQLdb
from nameko.rpc import rpc
def connect():
DBconnect = MySQLdb.connect(host='db',
user='devops',
passwd='devops101',
db='devops_db',
port=3306)
return DBconnect
def insert(id, firstname, lastname, subjectid, term, year):
DBconnect = connect()
cur = DBconnect.cursor()
cur.execute("INSERT INTO enroll (id, name, subjectid, term, year) VALUES (%s, %s, %s, %s, %s);", (id, firstname + ' ' + lastname, subjectid, term, year))
id = cur.lastrowid
DBconnect.commit()
DBconnect.close()
return id
class Service:
name = "enroll"
@rpc
def insert(self, id, firstname, lastname):
result = insert(id, firstname, lastname, '081102', 1, 2563)
result = insert(id, firstname, lastname, '520101', 1, 2563)
result = insert(id, firstname, lastname, '511100', 1, 2563)
result = insert(id, firstname, lastname, '517121', 1, 2563)
return result
- แก้ไขไฟล์ devops_db.sql
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";
CREATE TABLE `enroll` (
`id` int NOT NULL,
`name` varchar(255) NOT NULL,
`subjectid` varchar(255) NOT NULL,
`term` int NOT NULL,
`year` int NOT NULL
) ENGINE=InnoDB CHARSET=utf8 COLLATE utf8_general_ci;
ALTER TABLE `enroll`
ADD PRIMARY KEY (`id`, `subjectid`, `term`, `year`);
COMMIT;
- สร้าง Bridge network โดยตั้งชื่อเป็น enroll_network
docker network create enroll_network
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
7.Email Service
งานของ Email Service นั้นง่ายมากครับ เมื่อมีการลงทะเบียนแล้ว Email Service จะส่งข้อความยืนยันการลงทะเบียนกลับทาง Email โดยเราจะ Config docker-compose และไฟล์อื่นๆ ตามขั้นตอนต่อไปนี้
- แต่ก่อนที่จะติดต่อกับ Relay Host สำหรับการรับส่ง Email โดยใช้ Email Account เช่น ของ Google เราจะต้องมีการลงชื่อเข้าใช้ด้วยรหัสผ่านสำหรับแอป (app password) ตามคำแนะนำต่อไปนี้ >> การลงชื่อเข้าใช้ด้วยรหัสผ่านสำหรับแอป
- สร้าง Project ใหม่ ภายใน Folder email_dock ประกอบด้วย ไฟล์ และ Folder ดังต่อไปนี้
.
|__ docker-compose.yml
|__ python/
|__ Dockerfile
|__ requirements.txt
|__ rpc.py
- แก้ไข docker-compose.yml โดยเปลี่ยน RELAY_HOST, RELAY_USERNAME และ RELAY_PASSWORD ด้วยข้อมูล Remote SMTP Server ของตัวเอง
version: '3'
services:
email_rpc:
container_name: email_rpc
build: python/
restart: always
networks:
- microservice
- default
smtp:
container_name: email_smtp
image: bytemark/smtp
restart: always
environment:
RELAY_HOST: smtp.gmail.com
RELAY_PORT: 587
RELAY_USERNAME: [email protected]
RELAY_PASSWORD: xxx //Password จากการลงชื่อเข้าใช้ด้วย google app password
networks:
default:
external:
name: email_network
microservice:
external:
name: microservice_network
- แก้ไข Dockerfile ตามตัวอย่าง
FROM python:3.7.3-alpine3.8
RUN apk add --no-cache build-base
WORKDIR /app
COPY rpc.py .
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
CMD nameko run rpc --broker amqp://guest:guest@rabbitmq:5672
- แก้ไขไฟล์ requirements.txt
nameko==2.10.0
- แก้ไขไฟล์ rpc.py
import smtplib
from email.message import EmailMessage
from nameko.rpc import rpc
def send(id, firstname, lastname, email):
msg = EmailMessage()
text = "สวัสดีครับ คุณ " + firstname + " " + lastname + " รหัสนักศึกษา " + str(id) + " ได้ลงทะเบียนแล้ว"
msg.set_content(text)
msg['Subject'] = 'Register complete'
msg['To'] = email
s = smtplib.SMTP("smtp",25)
s.ehlo()
s.sendmail(from_addr = '[email protected]', to_addrs = email, msg = msg.as_string())
s.quit()
class Email:
name = "email"
@rpc
def send(self, id, firstname, lastname, email):
send(id, firstname, lastname, email)
- สร้าง Bridge network โดยตั้งชื่อเป็น email_network
docker network create email_network
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
- ดู Stacks (กลุ่มของ Container ที่ Config โดย Docker-compose) บน Docker ด้วย Portainer
- ทดลองเรียก API ผ่าน Browser ตาม URL ดังนี้
http://labxx.cpsudevops.com:7001/docs
- กด Try it out
- กด Execute
- ดู Record ที่บันทึกโดย Student Service จาก URL https://mydb.labxx.cpsudevops.com
- ดู Record ที่บันทึกโดย Enroll Service จาก URL https://mydb2.labxx.cpsudevops.com
- ดู email ใน Inbox
รายวิชา Dev-Ops and Cloud Engineering 101