เริ่มต้นกับ Microservice #1

ความแตกต่างของ Microservices และ Monolithic นั้นค่อนข้างชัดเจน ข้อเสียของ Monolithic คือเมื่อต้องการซ่อมแซมส่วนใด ก็จะมีผลกระทบกับระบบซอฟต์แวร์ทั้งหมด เนื่องจากมีการผูกติดกันอย่างแน่นหนา (Tightly Coupled) หรือเมื่อ Technology ใหม่เข้ามา และเราต้องการใช้ Technology ใหม่ก็ทำได้ยากกว่า Microservices เนื่องจากต้องรื้อทั้งระบบ แม้กระทั่งการ Scale ก็ยังทำได้ยากเนื่องจากต้อง Scale ทั้งระบบหรือแทบจะกล่าวได้ว่าสามารถ Scale ได้เฉพาะ แบบกระจายแบบ Round-Robin ได้เท่านั้นไม่สามารถ Scale ในรูปแบบ Partitioning ได้
Microservices ดีกว่า Monolithic ยังไงบ้าง?
- แก้ไขปัญหาความซับซ้อนของ Application
- Developer มีความเป็นอิสระในการเลือก Technology
- เพิ่ม Service ได้ง่าย
- Deploy ได้รวดเร็ว
- การ Scale ก็เป็นอิสระ
- เปลี่ยน Technology ได้ง่าย
แต่ Microservices และ Monolithic นั้นจริงแล้วก็มีข้อดีเสียอยู่นะครับใช่ว่า Microservices จะดีเสียทุกอย่าง อย่างไร Monolithic ก็มีข้อดีเราจึงเลือกใช้ Monolithic และ Microservices ให้เหมาะกับงานเพื่อให้ได้ประโยชน์สูงสุดในองค์กรของท่านครับ
RabbitMQ
เป็นพระเอกของงานนี้เลย ทำหน้าที่เป็นตัวกลางคอยรับส่ง Event ให้กับ Service ที่มา Subscribe Event นั้นๆ หรือเรียกว่าการทำงานแบบ Event-driven
mq_dock/docker-compose.yml
version: "2"
services:
rabbitmq:
image: "rabbitmq:management"
container_name: rabbitmq
restart: always
networks:
default:
external:
name: microservice_network
และสั่งรันไว้ตามปกติด้วย
docker-compose up -d
ต่อไปเราจะมาสร้างตัว Event กัน
register_gateway_dock
├── docker-compose.yml
└── python
├── Dockerfile
├── api.py
└── requirements.txt
docker-compose.yml
version: "3"services:register:container_name: register_gatewaybuild: python/restart: alwaysports:- "7001:80"networks:default:external:name: microservice_network
Dockerfile
FROM python:3.7.3-alpine3.8
RUN apk add --no-cache mariadb-dev build-base
WORKDIR /app
COPY api.py .
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
CMD uvicorn api:app --host 0.0.0.0 --port 80
requirements.txt
fastapi
uvicorn
pydantic
nameko
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'}
Student Service
Service ตัวแรกมีหน้าที่คอยเก็บข้อมูลนักศึกษา จะถูกเรียกใช้งานจาก RPC
student_dock
|__ 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.lab10.cpsudevops.com
LETSENCRYPT_HOST: mydb.lab10.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
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 DEFAULT CHARSET=latin1;
ALTER TABLE `student`
ADD PRIMARY KEY (`id`);
ALTER TABLE `student`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;
Enroll Service
เป็น Service เก็บข้อมูลการลงทะเบียน จะถูกเรียกใช้งานผ่าน RPC API จะเพิ่มรายวิชาเข้าไปยัง Database
enroll_dock
|__ 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.lab10.cpsudevops.com
LETSENCRYPT_HOST: mydb2.lab10.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
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 DEFAULT CHARSET=latin1;
ALTER TABLE `enroll`
ADD PRIMARY KEY (`id`, `subjectid`, `term`, `year`);
COMMIT;
Email Service
และมาถึง Service มีหน้าที่ส่งข้อความยืนยันการลงทะเบียนไปยัง E-mail ผู้สมัคร ซึ่งก็จะถูกเรียกใช้งานผ่านการเรียก RPC อีกเช่นเดิม
email_dock
|__ docker-compose.yml
|__ python/
|__ Dockerfile
|__ requirements.txt
|__ rpc.py
docker-compose.yml
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.live.com
RELAY_PORT: 587
RELAY_USERNAME: yourEmail@hotmail.com
RELAY_PASSWORD: yourPassword
networks:
default:
external:
name: email_network
microservice:
external:
name: microservice_network
ในส่วนนี้แก้ไขตามผู้ให้บริการ email ของท่าน จากตัวอย่างคือของ Hotmail
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
nameko
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 = 'yourEmail@hotmail.com', 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)
Note: ไม่ได้เขียนในส่วนให้ service แต่ละตัวเริ่มต้นการทำงาน อย่าลืมสั่งด้วย docker-compose up -d
หลังจากนั้นก็ทดลองเล่นได้ผ่าน FastAPI ที่ domainname:7001/docs





Reference
https://www.softnix.co.th/2018/08/09/microservices-in-10-minutes/
https://engineering.thinknet.co.th/microservices-communication-5cdde1b47273
https://blog.pjjop.org/build-microservice-on-docker-container/