Оригинальная ссылка:ewanvalentine.io, перевод одобрен авторомEwan Valentine уполномоченный.
В этом разделе подробно не рассказывается о Docker.Пересмотренное издание первой книги о докере
предисловие
В предыдущей статье мы использовали gRPC для первоначальной реализации нашего микросервиса, а в этом разделе мы докеризуем микросервис и представим фреймворк go-micro вместо gRPC, чтобы упростить реализацию сервиса.
Docker
задний план
Занимая преимущества облачных вычислений, микросервисная архитектура становится все более популярной, а ее облачно-распределенная операционная среда также выдвигает высокие требования к нашей разработке, тестированию и развертыванию.Контейнер (контейнер) — это решение.
В традиционной разработке программного обеспечения приложения развертываются непосредственно в системах с подготовленными средами и зависимостями или на физическом сервере в виртуальном кластере, управляемом Chef или Puppet. Такая схема развертывания не способствует горизонтальному расширению, например, для развертывания нескольких физических серверов необходимо устанавливать одинаковые зависимости, а затем развертывать их, что очень хлопотно.
vagrantХотя такие инструменты для управления несколькими виртуальными машинами делают развертывание проекта более проходным, на каждой виртуальной машине работает полноценная операционная система, которая потребляет ресурсы хоста и не подходит для разработки и развертывания микросервисов.
контейнер
характеристика
контейнерЭто упрощенная версия операционной системы, но она не запускает ядро или связанные с ним драйверы в нижней части системы. Она содержит только некоторые библиотеки, необходимые для выполнения. Несколько контейнеров совместно используют ядро хост-хоста, а несколько контейнеры изолированы друг от друга и дополняют друг друга. Вы можете обратиться к:Redhat topic
Преимущество
Вместо того, чтобы использовать полноценную операционную систему с целым набором ненужных компонентов, среда выполнения контейнера содержит только те зависимости, которые нужны коду. Кроме того, объем самого контейнера относительно невелик по сравнению с виртуальной машиной, например преимущества по сравнению с ubuntu 16.04 очевидны:
-
размер виртуальной машины
-
размер образа контейнера
Докер и контейнеры
Большинство людей думают, что контейнерная технология — это Docker, но на самом деле Docker — это всего лишь реализация контейнерной технологии, столь популярной из-за простоты работы и низкого порога обучения.
Докеризованные микросервисы
Dockerfile
Dockerfile для создания развертывания микросервиса
# 若运行环境是 Linux 则需把 alpine 换成 debian
# 使用最新版 alpine 作为基础镜像
FROM alpine:latest
# 在容器的根目录下创建 app 目录
RUN mkdir /app
# 将工作目录切换到 /app 下
WORKDIR /app
# 将微服务的服务端运行文件拷贝到 /app 下
ADD consignment-service /app/consignment-service
# 运行服务端
CMD ["./consignment-service"]
alpine — сверхлегкий дистрибутив Linux, предназначенный для веб-приложений в Docker. Это гарантирует, что большинство веб-приложений могут нормально работать, даже если оно содержит только необходимые файлы времени выполнения и зависимости, а размер образа составляет всего 4 МБ, что экономит 99,7% места по сравнению с приведенным выше Ubuntu 16.4:
Благодаря сверхлегкому характеру образа Docker развертывание и запуск микросервисов на нем требует очень мало ресурсов.
Скомпилируйте проект
Чтобы запустить наш микросервис на alpine, добавьте команду в Makefile:
build:
...
# 告知 Go 编译器生成二进制文件的目标环境:amd64 CPU 的 Linux 系统
GOOS=linux GOARCH=amd64 go build
# 根据当前目录下的 Dockerfile 生成名为 consignment-service 的镜像
docker build -t consignment-service .
нужно указывать вручнуюGOOS
иGOARCH
Значение , иначе файлы, скомпилированные в macOS, не могут быть запущены в контейнере alpine.
вdocker build
Упакуйте исполняемый файл программы consignment-service и его необходимую среду выполнения в образ, а затем прямо в докереrun
Образ может запускать микросервис.
Вы можете поделиться своим изображением сDockerHubотношения между ними аналогичны npm и nodejs, composer и PHP, зайдите на DockerHub, чтобы посмотреть, вы обнаружите, что многие отличные программы с открытым исходным кодом были докеризированы, обратитесь к речи:Willy Wonka of Containers
Подробнее о создании образов с помощью Docker см. в главе 4 книги «The First Docker Book».
Запуск докеризированных микросервисов
Продолжайте и добавьте команды в Makefile:
build:
...
run:
# 在 Docker alpine 容器的 50001 端口上运行 consignment-service 服务
# 可添加 -d 参数将微服务放到后台运行
docker run -p 50051:50051 consignment-service
Поскольку у Docker есть собственный независимый сетевой уровень, вам нужно указать порт, который сопоставляет порт контейнера с портом локальной машины, используя-p
можно указать такие параметры, как-p 8080:50051
Это должно сопоставить контейнерный порт 50051 с локальным портом 8080, обратите внимание, что порядок обратный. Больше ссылок:Документация по докеру
беги сейчасmake build && make run
Мы можем запустить наш микросервис в докере, В это время локальное выполнение клиентского кода микросервиса будет успешно вызывать микросервис в докере:
Go-micro
Почему бы не продолжать использовать gRPC?
хлопоты управления
В клиентском коде (consignment-cli/cli.go) мы вручную указываем адрес и порт сервера, и модифицировать его локально не очень хлопотно. Однако в производственной среде каждая служба может находиться не на одном хосте (распределенная и независимая работа), и если IP-адрес или рабочий порт какой-либо службы изменится после повторного развертывания, другие службы больше не смогут ее вызывать. Если у вас много сервисов, указывая IP и порт для вызова друг друга, управлять будет проблематично.
обнаружение службы
Для того чтобы решить проблему звонка между сервисами появился service discovery (обнаружение сервисов).Как реестр, он записывает IP и порт каждого микросервиса.Когда каждый микросервис выходит в онлайн, он будет там зарегистрирован, и будет логироваться отключается, когда он отключается.Другие службы могут найти службу по имени или идентификатору по аналогии с шаблоном фасада.
Чтобы не изобретать велосипед, мы напрямую используем фреймворк go-micro, реализующий регистрацию сервиса.
Установить
go get -u github.com/micro/protobuf/proto
go get -u github.com/micro/protobuf/protoc-gen-go
Чтобы использовать собственный подключаемый модуль компилятора go-micro, измените команду protoc в Makefile:
build:
# 不再使用 grpc 插件
protoc -I. --go_out=plugins=micro:$(GOPATH)/src/shippy/consignment-service proto/consignment/consignment.proto
Сервер использует go-micro
Вы заметите, что восстановленный файл consignment.pb.go сильно отличается. Измените код сервера main.go, чтобы использовать go-micro.
package main
import (
pb "shippy/consignment-service/proto/consignment"
"context"
"log"
"github.com/micro/go-micro"
)
//
// 仓库接口
//
type IRepository interface {
Create(consignment *pb.Consignment) (*pb.Consignment, error) // 存放新货物
GetAll() []*pb.Consignment // 获取仓库中所有的货物
}
//
// 我们存放多批货物的仓库,实现了 IRepository 接口
//
type Repository struct {
consignments []*pb.Consignment
}
func (repo *Repository) Create(consignment *pb.Consignment) (*pb.Consignment, error) {
repo.consignments = append(repo.consignments, consignment)
return consignment, nil
}
func (repo *Repository) GetAll() []*pb.Consignment {
return repo.consignments
}
//
// 定义微服务
//
type service struct {
repo Repository
}
//
// 实现 consignment.pb.go 中的 ShippingServiceHandler 接口
// 使 service 作为 gRPC 的服务端
//
// 托运新的货物
// func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment) (*pb.Response, error) {
func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment, resp *pb.Response) error {
// 接收承运的货物
consignment, err := s.repo.Create(req)
if err != nil {
return err
}
resp = &pb.Response{Created: true, Consignment: consignment}
return nil
}
// 获取目前所有托运的货物
// func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest) (*pb.Response, error) {
func (s *service) GetConsignments(ctx context.Context, req *pb.GetRequest, resp *pb.Response) error {
allConsignments := s.repo.GetAll()
resp = &pb.Response{Consignments: allConsignments}
return nil
}
func main() {
server := micro.NewService(
// 必须和 consignment.proto 中的 package 一致
micro.Name("go.micro.srv.consignment"),
micro.Version("latest"),
)
// 解析命令行参数
server.Init()
repo := Repository{}
pb.RegisterShippingServiceHandler(server.Server(), &service{repo})
if err := server.Run(); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Реализация go-micro имеет 3 основных отличия от gRPC:
Процесс создания RPC-сервера
micro.NewService(...Option)
Упрощает процесс регистрации микросервисов,micro.Run()
также упрощенныйgRPCServer.Serve()
, вам больше не нужно вручную создавать TCP-соединение и прослушивать.
Интерфейс микросервиса
Обратите внимание на строки 47 и 59 в коде, вы обнаружите, что go-micro упоминает параметр ответа Response в качестве входного параметра, возвращает только ошибку и интегрирует gRPC.Четыре режима работы
Управление операционными адресами
Порт прослушивания службы не запрограммирован жестко в коде, go-mirco автоматически использует переменные в системе или командной строке.MICRO_SERVER_ADDRESS
адрес
Обновите Makefile соответствующим образом
run:
docker run -p 50051:50051 \
-e MICRO_SERVER_ADDRESS=:50051 \
-e MICRO_REGISTRY=mdns \
consignment-service
Параметр -e используется для установки переменных окружения в образе, гдеMICRO_REGISTRY=mdns
сделает go-micro доступным локальноmdnsМногоадресная рассылка как промежуточный уровень для обнаружения услуг. Обычно используется в производственных средахConsulилиEtcdВместо того, чтобы выполнять поиск служб с помощью mdns, упростите локальную разработку.
выполнить сейчасmake build && make run
, у вашего консигнационного сервиса есть функция обнаружения сервиса.
Клиент использует go-micro
Нам нужно обновить клиентский код, чтобы использовать go-micro для вызова микросервиса:
func main() {
cmd.Init()
// 创建微服务的客户端,简化了手动 Dial 连接服务端的步骤
client := pb.NewShippingServiceClient("go.micro.srv.consignment", microclient.DefaultClient)
...
}
беги сейчасgo run cli.go
Будет сообщено об ошибке:
Потому что сервер работает в Docker, а у Docker есть свои независимые mdns, что несовместимо с mdns хоста Mac. Клиент также Dockerized, так что сервер и клиент находятся на одном сетевом уровне, а mdns используется для беспрепятственного обнаружения служб.
Докеризированный клиент
Создайте Dockerfile для клиента
FROM alpine:latest
RUN mkdir -p /app
WORKDIR /app
# 将当前目录下的货物信息文件 consignment.json 拷贝到 /app 目录下
ADD consignment.json /app/consignment.json
ADD consignment-cli /app/consignment-cli
CMD ["./consignment-cli"]
Создать файлconsignment-cli/Makefile
build:
GOOS=linux GOARCH=amd64 go build
docker build -t consignment-cli .
run:
docker run -e MICRO_REGISTRY=mdns consignment-cli
вызов микросервиса
воплощать в жизньmake build && make run
, вы можете видеть, что клиент успешно вызвал RPC:
Примечание: код переводчика пока не интегрировал Golang в Dockerfile.Заинтересованные читатели могут обратиться к исходному тексту.
VesselService
Вышеуказанная консигнационная служба отвечает за запись информации о консигнации товаров. Теперь создайте вторую микрослужбу-судно-службу, чтобы выбрать подходящее грузовое судно для доставки товаров. Связь выглядит следующим образом:
Груз, состоящий из трех контейнеров в файле consignment.json, в настоящее время может управлять информацией о грузе через службу консигнации, а теперь использовать службу судна, чтобы проверить, может ли грузовое судно удержать этот груз.
Создать файл protobuf
// vessel-service/proto/vessel/vessel.proto
syntax = "proto3";
package go.micro.srv.vessel;
service VesselService {
// 检查是否有能运送货物的轮船
rpc FindAvailable (Specification) returns (Response) {
}
}
// 每条货轮的熟悉
message Vessel {
string id = 1; // 编号
int32 capacity = 2; // 最大容量(船体容量即是集装箱的个数)
int32 max_weight = 3; // 最大载重
string name = 4; // 名字
bool available = 5; // 是否可用
string ower_id = 6; // 归属
}
// 等待运送的货物
message Specification {
int32 capacity = 1; // 容量(内部集装箱的个数)
int32 max_weight = 2; // 重量
}
// 货轮装得下的话
// 返回的多条货轮信息
message Response {
Vessel vessel = 1;
repeated Vessel vessels = 2;
}
Создайте Makefile и Dockerfile
создать сейчасvessel-service/Makefile
Чтобы скомпилировать проект:
build:
protoc -I. --go_out=plugins=micro:$(GOPATH)/src/shippy/vessel-service proto/vessel/vessel.proto
# dep 工具暂不可用,直接手动编译
GOOS=linux GOARCH=amd64 go build
docker build -t vessel-service .
run:
docker run -p 50052:50051 -e MICRO_SERVER_ADDRESS=:50051 -e MICRO_REGISTRY=mdns vessel-service
Обратите внимание, что второй микросервис работает на порту 50052 хост-машины (macOS), а 50051 уже занят первым.
Теперь создайте Dockerfile для контейнеризации судового сервиса:
# 暂未将 Golang 集成到 docker 中
FROM alpine:latest
RUN mkdir /app
WORKDIR /app
ADD vessel-service /app/vessel-service
CMD ["./vessel-service"]
Реализация логики микросервисов грузовых кораблей
package main
import (
pb "shippy/vessel-service/proto/vessel"
"github.com/pkg/errors"
"context"
"github.com/micro/go-micro"
"log"
)
type Repository interface {
FindAvailable(*pb.Specification) (*pb.Vessel, error)
}
type VesselRepository struct {
vessels []*pb.Vessel
}
// 接口实现
func (repo *VesselRepository) FindAvailable(spec *pb.Specification) (*pb.Vessel, error) {
// 选择最近一条容量、载重都符合的货轮
for _, v := range repo.vessels {
if v.Capacity >= spec.Capacity && v.MaxWeight >= spec.MaxWeight {
return v, nil
}
}
return nil, errors.New("No vessel can't be use")
}
// 定义货船服务
type service struct {
repo Repository
}
// 实现服务端
func (s *service) FindAvailable(ctx context.Context, spec *pb.Specification, resp *pb.Response) error {
// 调用内部方法查找
v, err := s.repo.FindAvailable(spec)
if err != nil {
return err
}
resp.Vessel = v
return nil
}
func main() {
// 停留在港口的货船,先写死
vessels := []*pb.Vessel{
{Id: "vessel001", Name: "Boaty McBoatface", MaxWeight: 200000, Capacity: 500},
}
repo := &VesselRepository{vessels}
server := micro.NewService(
micro.Name("go.micro.srv.vessel"),
micro.Version("latest"),
)
server.Init()
// 将实现服务端的 API 注册到服务端
pb.RegisterVesselServiceHandler(server.Server(), &service{repo})
if err := server.Run(); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Взаимодействие Cargo Service с Cargo Ship Service
теперь нужно изменитьconsignent-service/main.go
, сделайте это как клиент, позвоните в судоходную службу, чтобы узнать, есть ли подходящее судно для перевозки груза.
// consignent-service/main.go
package main
import (...)
// 定义微服务
type service struct {
repo Repository
// consignment-service 作为客户端调用 vessel-service 的函数
vesselClient vesselPb.VesselServiceClient
}
// 实现 consignment.pb.go 中的 ShippingServiceHandler 接口,使 service 作为 gRPC 的服务端
func (s *service) CreateConsignment(ctx context.Context, req *pb.Consignment, resp *pb.Response) error {
// 检查是否有适合的货轮
vReq := &vesselPb.Specification{
Capacity: int32(len(req.Containers)),
MaxWeight: req.Weight,
}
vResp, err := s.vesselClient.FindAvailable(context.Background(), vReq)
if err != nil {
return err
}
// 货物被承运
log.Printf("found vessel: %s\n", vResp.Vessel.Name)
req.VesselId = vResp.Vessel.Id
consignment, err := s.repo.Create(req)
if err != nil {
return err
}
resp.Created = true
resp.Consignment = consignment
return nil
}
// ...
func main() {
// ...
// 解析命令行参数
server.Init()
repo := Repository{}
// 作为 vessel-service 的客户端
vClient := vesselPb.NewVesselServiceClient("go.micro.srv.vessel", server.Client())
pb.RegisterShippingServiceHandler(server.Server(), &service{repo, vClient})
if err := server.Run(); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Добавляйте груз и бегите
возобновитьconsignment-cli/consignment.json
Груз, упакованный в три контейнера, увеличился в весе и вместимости.
{
"description": "This is a test consignment",
"weight": 55000,
"containers": [
{
"customer_id": "cust001",
"user_id": "user001",
"origin": "Manchester, United Kingdom"
},
{
"customer_id": "cust002",
"user_id": "user001",
"origin": "Derby, United Kingdom"
},
{
"customer_id": "cust005",
"user_id": "user001",
"origin": "Sheffield, United Kingdom"
}
]
}
На этом этапе мы завершим выполнение трех процессов: консигнация-кли, консигнация-сервис, судно-сервис.
Когда клиент-пользователь запрашивает партию товара, грузовая служба проверяет, не превышают ли грузоподъемность и вес стандарт службы грузового судна, а затем отправляет:
Суммировать
В этом разделе более простой в использовании go-micro заменяет gRPC и Dockerizes микросервисы. Наконец, был создан микросервис фрахтовщика для доставки товаров, который успешно взаимодействовал с микросервисом фрахтовщика.
Однако данные о грузе хранятся в файле consignment.json.В третьем разделе мы храним эти данные в базе данных MongoDB и используем ORM для работы с данными в коде.В то же время мы используем docker-compose для объединить их после докеризации в микросервис.