É hora de enfrentar os contêineres
Primeiro de tudo, usamos a imagem mais recente do Linux Alpine. Linux Alpine é uma distribuição Linux leve, projetada e otimizada para a execução de aplicativos Web no Docker. Em outras palavras, o Linux Alpine possui dependências e funcionalidades suficientes para executar a maioria dos aplicativos. Isso significa que o tamanho da imagem é de cerca de 8 MB!
Comparado a, digamos ... uma máquina virtual Ubuntu com capacidade de cerca de 1 GB, é por isso que as imagens do Docker se tornaram mais naturais para microsserviços e computação em nuvem.
Portanto, agora espero que você veja valor na contêiner e que possamos iniciar o "Dockerising" nosso primeiro serviço. Vamos criar um serviço de
consignação / Dockerfile $ Dockerfile .
Primeira parteRepositório original do EwanValentineArtigo originalNo Dockerfile, adicione o seguinte:
FROM alpine:latest RUN mkdir /app WORKDIR /app ADD consignment-service /app/consignment-service CMD ["./consignment-service"]
Em seguida, criamos um novo diretório para hospedar nosso aplicativo. Em seguida, adicionamos nosso binário compilado ao contêiner do Docker e o executamos.
Agora, vamos atualizar o registro de compilação do nosso Makefile para criar uma imagem do Docker.
build: ... GOOS=linux GOARCH=amd64 go build docker build -t consignment .
Adicionamos mais duas etapas e gostaria de explicá-las com mais detalhes. Primeiro de tudo, criamos nosso binário Go. No entanto, você notará duas variáveis de ambiente antes de executar o $ go build. GOOS e GOARCH permitem que você faça uma compilação cruzada de seu binário para outro sistema operacional. Como estou desenvolvendo um Macbook, não consigo compilar o executável go e executá-lo em um contêiner Docker que usa Linux. O binário não terá sentido no seu contêiner do Docker e gerará um erro.
A segunda etapa que adicionei é o processo de criação do docker. O Docker lerá o seu Dockerfile e criará uma imagem denominada consignment-service, o ponto indica o caminho do diretório, portanto, aqui apenas queremos que o processo de criação observe o diretório atual.
Vou adicionar uma nova entrada ao nosso Makefile:
run: docker run -p 50051:50051 shippy-service-consignment
Aqui, lançamos nossa imagem do docker abrindo a porta 50051. Como o Docker opera em uma camada de rede separada, é necessário redirecionar a porta. Por exemplo, se você deseja iniciar este serviço na porta 8080, altere o argumento -p para 8080: 50051. Você também pode executar o contêiner em segundo plano, incluindo o sinalizador -d. Por exemplo, a
janela de encaixe execute -d -p 50051: 50051 consignment-service .
Execute
$ make run e , em um painel terminal separado, novamente
$ go run main.go e verifique se ele ainda funciona.
Ao executar a construção do $ docker, você incorpora seu código e tempo de execução na imagem. Imagens do Docker são imagens portáteis do seu ambiente e de suas dependências. Você pode compartilhar imagens do Docker postando-as no Docker Hub. Que é semelhante ao npm ou ao repositório yum para imagens do docker. Quando você define FROM no seu Dockerfile, solicita ao Docker que puxe essa imagem do repositório do Docker para usar como base. Você pode expandir e redefinir partes desse arquivo base, redefinindo-as como desejar. Não publicaremos nossas imagens do docker, mas fique à vontade para navegar no repositório do docker e observe que quase todos os softwares já foram empacotados em contêineres. Algumas coisas realmente maravilhosas foram encaixotadas.
Cada anúncio no Dockerfile é armazenado em cache na primeira vez em que é criado. Isso elimina a necessidade de reconstruir todo o tempo de execução toda vez que você faz alterações. A janela de encaixe é inteligente o suficiente para descobrir quais detalhes foram alterados e quais precisam ser reconstruídos. Isso torna o processo de criação incrivelmente rápido.
Chega de contêineres! Vamos voltar ao nosso código.
Ao criar o serviço gRPC, há muitos códigos padrão para a criação de conexões, e você precisa codificar o local do endereço do serviço no cliente ou em outro serviço para que ele possa se conectar a ele. Isso é difícil porque, quando você inicia os serviços na nuvem, eles podem não usar o mesmo host ou o endereço ou o ip podem mudar após a reimplantação do serviço.
É aqui que o serviço de descoberta entra em ação. O serviço de descoberta atualiza o diretório de todos os seus serviços e seus locais. Cada serviço é registrado em tempo de execução e cancela o registro de perto. Cada serviço recebe um nome ou identificador. Portanto, mesmo que possa ter um novo endereço IP ou um endereço de host, desde que o nome do serviço permaneça o mesmo, você não precisará atualizar as chamadas para esse serviço de outros serviços.
Como regra, existem muitas abordagens para esse problema, mas, como a maioria das coisas na programação, se alguém já lidou com esse problema, não faz sentido reinventar a roda. @Chuhnk (Asim Aslam), criador do
Go-micro , resolve esses problemas com uma clareza fantástica e facilidade de uso. Ele, sozinho, produz software fantástico. Por favor, considere ajudá-lo se você gosta do que vê!
Go micro
O Go-micro é uma poderosa estrutura de microsserviço escrita em Go, para uso, em grande parte, com o Go. No entanto, você pode usar o Sidecar para interagir com outros idiomas.
O Go-micro possui recursos úteis para criar microsserviços no Go. Mas começaremos talvez com o problema mais comum que ele resolve, e essa é a descoberta de um serviço.
Precisamos fazer várias atualizações em nosso serviço para trabalhar com o go-micro. O Go-micro se integra como um plug-in Protoc, neste caso substituindo o plug-in gRPC padrão que estamos usando no momento. Então, vamos começar substituindo isso em nosso Makefile.
Certifique-se de instalar as dependências go-micro:
go get -u github.com/micro/protobuf/{proto,protoc-gen-go}
Atualize nosso Makefile para usar o plug-in go-micro em vez do plug-in gRPC:
build: protoc -I. --go_out=plugins=micro:. \ proto/consignment/consignment.proto GOOS=linux GOARCH=amd64 go build docker build -t consignment . run: docker run -p 50051:50051 shippy-service-consignment
Agora precisamos atualizar nosso shippy-service-consignment / main.go para usar o go-micro. Isso abstrai a maior parte do nosso código gRPC anterior. Ele processa facilmente o registro e acelera a gravação de um serviço.
shippy-service-consignment / main.go A principal mudança aqui é a maneira como criamos o servidor gRPC, que foi abstraído de mico.NewService (), que lida com o registro do nosso serviço. E, finalmente, a função service.Run (), que processa a própria conexão. Como antes, registramos nossa implementação, mas desta vez com um método ligeiramente diferente.
A segunda maior mudança diz respeito aos próprios métodos de serviço: os argumentos e os tipos de respostas são levemente modificados para aceitar a solicitação e as estruturas de resposta como argumentos, e agora retornam apenas um erro. Em nossos métodos, definimos a resposta que os processos vão micro.
Finalmente, não programamos mais a porta. O Go-micro deve ser configurado usando variáveis de ambiente ou argumentos de linha de comando. Para definir o endereço, use MICRO_SERVER_ADDRESS =: 50051. Por padrão, a Micro usa mdns (multicast dns) como um broker de descoberta de serviço para uso local. Normalmente você não usa mdns para descobrir serviços em um ambiente de produção, mas queremos evitar a execução de algo como Consul ou etcd localmente para teste. Mais sobre isso mais tarde.
Vamos atualizar nosso Makefile para refletir isso.
build: protoc -I. --go_out=plugins=micro:. \ proto/consignment/consignment.proto GOOS=linux GOARCH=amd64 go build docker build -t consignment . run: docker run -p 50051:50051 \ -e MICRO_SERVER_ADDRESS=:50051 \ shippy-service-consignment
-e é o sinalizador da variável de ambiente, permite passar variáveis de ambiente para o contêiner do Docker. Você deve ter um sinalizador para cada variável, por exemplo -e ENV = teste -e DB_HOST = localhost, etc.
Agora, se você executar $ make run, terá um serviço Dockerised com descoberta de serviço. Então, vamos atualizar nossa ferramenta Cli para usar isso.
consignação-cli package main import ( "encoding/json" "io/ioutil" "log" "os" "context" pb "github.com/EwanValentine/shippy-service-consignment/proto/consignment" micro "github.com/micro/go-micro" ) const ( address = "localhost:50051" defaultFilename = "consignment.json" ) func parseFile(file string) (*pb.Consignment, error) { var consignment *pb.Consignment data, err := ioutil.ReadFile(file) if err != nil { return nil, err } json.Unmarshal(data, &consignment) return consignment, err } func main() { service := micro.NewService(micro.Name("shippy.cli.consignment")) service.Init() client := pb.NewShippingServiceClient("shippy.service.consignment", service.Client())
Aqui, importamos as bibliotecas go-micro para criar clientes e substituímos o código de conexão existente pelo código go-micro client, que usa a permissão do serviço em vez de se conectar diretamente ao endereço.
No entanto, se você executar isso, ele não funcionará. Isso ocorre porque agora estamos lançando nosso serviço no contêiner do Docker, que possui seus próprios mdns, separado do host mdns que estamos usando no momento. A maneira mais fácil de corrigir isso é garantir que o serviço e o cliente estejam em execução no dockerland, para que ambos trabalhem no mesmo host e usem a mesma camada de rede. Então, vamos criar make consignment-cli / Makefile e criar algumas entradas.
build: GOOS=linux GOARCH=amd64 go build docker build -t shippy-cli-consignment . run: docker run shippy-cli-consignment
Como antes, queremos construir nosso binário para Linux. Quando lançamos nossa imagem do docker, queremos passar uma variável de ambiente para fornecer o comando go-micro para usar mdns.
Agora vamos criar um Dockerfile para nossa ferramenta CLI:
FROM alpine:latest RUN mkdir -p /app WORKDIR /app ADD consignment.json /app/consignment.json ADD consignment-cli /app/consignment-cli CMD ["./shippy-cli-consignment"]
Isso é muito semelhante ao nosso serviço Dockerfile, exceto que ele também extrai nosso arquivo de dados json.
Agora, quando você executar $ make run em sua remessa shippy-cli, deverá ver Created: true, como antes.
Agora, parece que é hora de dar uma olhada no novo recurso do Docker: construções em vários estágios. Isso nos permite usar várias imagens do Docker em um arquivo Docker.
Isso é especialmente útil no nosso caso, pois podemos usar uma imagem para criar nosso arquivo binário com todas as dependências corretas. E então use a segunda imagem para iniciá-la. Vamos tentar isso, vou deixar comentários detalhados junto com o código:
serviço de consignação / Dockerfile # consignment-service/Dockerfile # golang, # . `as builder`, # , . FROM golang:alpine as builder RUN apk --no-cache add git # gopath WORKDIR /app/shippy-service-consignment # COPY . . RUN go mod download # , # Alpine. RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o shippy-service-consignment # FROM, # Docker . FROM alpine:latest # , - RUN apk --no-cache add ca-certificates # , . RUN mkdir /app WORKDIR /app # , , # `builder` # , # , , # . ! COPY --from=builder /app/shippy-service-consignment/shippy-service-consignment . # ! # # run time . CMD ["./shippy-service-consignment"]
Agora vou passar para outros arquivos do Docker e adotar essa nova abordagem. Ah, e não esqueça de remover $ go build dos seus Makefiles!
Serviço de navio
Vamos criar um segundo serviço. Temos um serviço (shippy-service-consignment), que lida com a coordenação do lote de contêineres com o navio, o que é mais adequado para esse lote. Para corresponder ao nosso lote, devemos enviar o peso e o número de contêineres para o nosso novo serviço de navio, que encontrará um navio capaz de lidar com esse lote.
Crie um novo diretório em seu diretório raiz
$ mkdir ship-service , agora crie um subdiretório para nossa nova definição de serviços protobuf,
$ mkdir -p shippy-service-ship / proto / ship . Agora vamos criar um novo arquivo protobuf,
$ touch shippy-service-ship / proto / ship / ship.proto .
Como a definição de protobuf é realmente o núcleo do design de nosso software, vamos começar com ela.
navio / navio.proto // shippy-service-vessel/proto/vessel/vessel.proto syntax = "proto3"; package 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 owner_id = 6; } message Specification { int32 capacity = 1; int32 max_weight = 2; } message Response { Vessel vessel = 1; repeated Vessel vessels = 2; }
Como você pode ver, isso é muito semelhante ao nosso primeiro serviço. Criamos um serviço com um método rpc chamado FindAvailable. Isso requer um tipo de especificação e retorna um tipo de resposta. O tipo de resposta retorna o tipo de embarcação ou vários navios usando um campo de repetição.
Agora precisamos criar um Makefile para lidar com nossa lógica de construção e nosso script de inicialização.
$ touch shippy-service-ship / Makefile . Abra este arquivo e adicione o seguinte:
// vessel-service/Makefile build: protoc -I. --go_out=plugins=micro:. \ proto/vessel/vessel.proto docker build -t shippy-service-vessel . run: docker run -p 50052:50051 -e MICRO_SERVER_ADDRESS=:50051 shippy-service-vessel
Isso é quase idêntico ao primeiro Makefile que criamos para nosso serviço de consignação, no entanto, observe que os nomes dos serviços e portas mudaram um pouco. Como não podemos executar dois contêineres de dock na mesma porta, usamos o encaminhamento de porta do Dockers para que esse serviço redirecione de 50051 para 50052 na rede host.
Agora precisamos de um Dockerfile usando nosso novo formato de vários estágios:
# vessel-service/Dockerfile FROM golang:alpine as builder RUN apk --no-cache add git WORKDIR /app/shippy-service-vessel COPY . . RUN go mod download RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o shippy-service-vessel FROM alpine:latest RUN apk --no-cache add ca-certificates RUN mkdir /app WORKDIR /app COPY --from=builder /app/shippy-service-vessel . CMD ["./shippy-service-vessel"]
Por fim, podemos escrever nossa implementação:
serviço de embarcação / main.go Agora vamos para a parte interessante. Quando criamos uma remessa, precisamos alterar nosso serviço de manuseio de carga para entrar em contato com o serviço de pesquisa de navios, encontrar o navio e atualizar o parâmetro ship_id na remessa criada:
shippy / consignment-service / main.go package main import ( "context" "fmt" "log" "sync" pb "github.com/EwanValentine/shippy-service-consignment/proto/consignment" vesselProto "github.com/EwanValentine/shippy-service-vessel/proto/vessel" "github.com/micro/go-micro" ) const ( port = ":50051" ) type repository interface { Create(*pb.Consignment) (*pb.Consignment, error) GetAll() []*pb.Consignment }
Aqui, criamos uma instância do cliente para o nosso serviço de remessa, o que nos permite usar o nome do serviço, ou seja, shipy.service.vessel para chamar o serviço do navio como cliente e interagir com seus métodos.
Nesse caso, apenas um método (FindAvailable). Enviamos o peso do lote juntamente com o número de contêineres que queremos enviar, como uma especificação para o serviço do navio. O que nos devolve a embarcação correspondente a esta especificação.Atualize o arquivo consignment-cli / consignment.json, exclua ship_id codificado, porque queremos confirmar que nosso serviço de pesquisa de navios está funcionando. Além disso, vamos adicionar mais alguns contêineres e aumentar o peso. Por exemplo:
{ "description": " ", "weight": 55000, "containers": [ { "customer_id": "_001", "user_id": "_001", "origin": "--" }, { "customer_id": "_002", "user_id": "_001", "origin": "" }, { "customer_id": "_003", "user_id": "_001", "origin": "" } ] }
Agora execute $ make build && make run em consignment-cli. Você deverá ver uma resposta com uma lista dos produtos criados. Nos seus grupos, você deve ver que o parâmetro ship_id está definido.Portanto, temos dois microsserviços interconectados e uma interface de linha de comando!Na próxima parte desta série, consideraremos salvar alguns desses dados usando o MongoDB. Também adicionaremos um terceiro serviço e usaremos o docker-compose para gerenciar localmente nosso crescente ecossistema de contêineres.Parte IRepositório Original EwanValentine