与Docker和Kubernetes集成的简单单体设计

149 阅读4分钟

多年来,我们一直投入大量的个人时间和精力,与大家分享我们的知识。然而,我们现在需要你的帮助来维持这个博客的运行。你所要做的就是点击网站上的一个广告,否则它将由于托管等费用而不幸被关闭。谢谢你。

这只是一个单库设计的例子,多语言被保存在同一个资源库中。每种语言都有自己的文件夹。你也可以把monorepo分成不同的语言,而不是把所有的语言放在同一个版本库里。这一切都取决于你和你的需求。

在这里,我们将使用Go语言来给你一个概念。将会有三个服务,每个服务都可以相互对话。当涉及到测试时,我们将建立一个Docker镜像,其中包含一个二进制文件,你通过服务名称来启动它。然后,我们将把这个镜像部署到Kubernetes生产集群,以运行所有三个服务。所有服务都有端点供我们从外部调用。同时,也会有一些端点用于服务与服务之间的通信。

你需要记住的是,鉴于这是一个假的/未完成的例子,会有一些重复和不太重视的部分。主要的一点是给你一个概念!

结构

描述

├── go    # where go stuff lives
├── doc   # where documentation lives (meant to cover all languages)
├── infra # where infrastructure stuff lives (meant to cover all languages)
├── js    # where javascript stuff lives
└── php   # where php stuff lives

例子

├── .gitignore
├── go
│   ├── cmd
│   │   └── monorepo
│   │       └── main.go
│   ├── go.mod
│   ├── pkg
│   │   └── client
│   │       └── http.go
│   └── svc
│       ├── account
│       │   └── main.go
│       ├── exam
│       │   └── main.go
│       └── student
│           └── main.go
├── infra
│   ├── dev
│   │   ├── Makefile
│   │   └── docker
│   │       └── docker-compose.yaml
│   ├── prod
│   │   ├── Makefile
│   │   ├── docker
│   │   │   └── go
│   │   │       └── Dockerfile
│   │   └── k8s
│   │       └── go
│   │           ├── account.yaml
│   │           ├── exam.yaml
│   │           └── student.yaml
│   └── qa
│       ├── docker
│       └── k8s
├── js
│   └── ... put your stuff here
└── php
    └── ... put your stuff here

文件

.gitignore

go/bin

dev/docker-compose.yaml

version: "3.4"

services:

  monorepo-mysql:
    container_name: "monorepo-mysql"
    image: "mysql:5.7.24"
    command:
      - "--character-set-server=utf8mb4"
      - "--collation-server=utf8mb4_unicode_ci"
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: "root"

dev/Makefile

.PHONY: help
help: ## Display available commands.
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

# DOCKER -----------------------------------------------------------------------

.PHONY: dev-docker-up
dev-docker-up: ## Bring dev environment up in attached mode.
	docker-compose -f docker/docker-compose.yaml up --build

.PHONY: dev-docker-down
dev-docker-down: ## Stop and clear dev environment.
	docker-compose -f docker/docker-compose.yaml down
	docker system prune --volumes --force

.PHONY: dev-docker-config
dev-docker-config: ## Echo dev environment config.
	docker-compose -f docker/docker-compose.yaml config

prod/Dockerfile

FROM golang:1.17.5-alpine3.15 as build
WORKDIR /source
COPY . .
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o monorepo cmd/monorepo/main.go

FROM alpine:3.15
COPY --from=build /source/monorepo /monorepo
EXPOSE 8000
ENTRYPOINT ["./monorepo", "--svc"]

prod/Makefile

.PHONY: help
help: ## Display available commands.
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

# CI/CD ------------------------------------------------------------------------

.PHONY: docker-push-go
docker-push-go: ## Build, tag and push go image to registry then clean up.
	DOCKER_BUILDKIT=0 docker build -t you/monorepo-go:latest -f docker/go/Dockerfile ../../go
	docker push you/monorepo-go:latest
	docker rmi you/monorepo-go:latest
	docker system prune --volumes --force

.PHONY: k8s-deploy-go
k8s-deploy-go: ## Deploy go applications.
	kubectl apply -f k8s/go/account.yaml
	kubectl apply -f k8s/go/exam.yaml
	kubectl apply -f k8s/go/student.yaml

prod/k8s文件

我不打算复制所有三个yaml文件,但你可以。如果你这样做,只需用studentexam 替换account 字样。

apiVersion: v1
kind: Service
metadata:
  name: svc-account
  namespace: prod
spec:
  type: ClusterIP
  selector:
    app: account
  ports:
    - port: 8000
      targetPort: 8000

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dep-account
  namespace: prod
  labels:
    app: account
spec:
  replicas: 1
  selector:
    matchLabels:
      app: account
  template:
    metadata:
      labels:
        app: account
    spec:
      containers:
        - name: go
          image: you/monorepo-go:latest
          args:
            - account
          ports:
            - containerPort: 8000

go/cmd/main.go

package main

import (
	"flag"
	"fmt"
	"log"
	"os"

	"monorepo/svc/account"
	"monorepo/svc/exam"
	"monorepo/svc/student"
)

const usage = `Description: Service to run
Usage: %s [options]
Options:
`

func main() {
	var svc string

	flag.StringVar(&svc, "svc", svc, "Service name.")
	flag.Usage = func() {
		_, _ = fmt.Fprintf(flag.CommandLine.Output(), usage, os.Args[0])
		flag.PrintDefaults()
	}
	flag.Parse()

	switch svc {
	case "account":
		account.Start()
	case "exam":
		exam.Start()
	case "student":
		student.Start()
	}

	log.Println("Unknown service")
}

go/pkg/http.go

package client

import (
	"net/http"
)

func HTTPRequest(uri string) error {
	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return err
	}

	if _, err := http.DefaultTransport.RoundTrip(req); err != nil {
		return err
	}

	return nil
}

go/account/main.go

package account

import (
	"log"
	"net/http"

	"monorepo/pkg/client"
)

func Start() {
	rtr := http.DefaultServeMux
	rtr.HandleFunc("/pay-debt", func(http.ResponseWriter, *http.Request) {
		log.Println("/pay-debt")

		if err := client.HTTPRequest("http://svc-exam:8000/unlock-results"); err != nil {
			log.Println(err)
		}
	})

	srv := &http.Server{Handler: rtr, Addr: "0.0.0.0:8000"}
	if err := srv.ListenAndServe(); err != nil {
		log.Fatalln(err)
	}
}

go/exam/main.go

package exam

import (
	"log"
	"net/http"

	"monorepo/pkg/client"
)

func Start() {
	rtr := http.DefaultServeMux
	rtr.HandleFunc("/unlock-results", func(http.ResponseWriter, *http.Request) {
		log.Println("/unlock-results")
	})
	rtr.HandleFunc("/publish-results", func(http.ResponseWriter, *http.Request) {
		log.Println("/publish-results")

		if err := client.HTTPRequest("http://svc-student:8000/publish-results"); err != nil {
			log.Println(err)
		}
	})

	srv := &http.Server{Handler: rtr, Addr: "0.0.0.0:8000"}
	if err := srv.ListenAndServe(); err != nil {
		log.Fatalln(err)
	}
}

go/student/main.go

package student

import (
	"log"
	"net/http"

	"monorepo/pkg/client"
)

func Start() {
	rtr := http.DefaultServeMux
	rtr.HandleFunc("/pay-debt", func(http.ResponseWriter, *http.Request) {
		log.Println("/pay-debt")

		if err := client.HTTPRequest("http://svc-account:8000/pay-debt"); err != nil {
			log.Println(err)
		}
	})
	rtr.HandleFunc("/publish-results", func(http.ResponseWriter, *http.Request) {
		log.Println("/publish-results")
	})

	srv := &http.Server{Handler: rtr, Addr: "0.0.0.0:8000"}
	if err := srv.ListenAndServe(); err != nil {
		log.Fatalln(err)
	}
}

脚本

module monorepo

go 1.17

Docker推送

运行monorepo/infra/prod$ make docker-push-go ,为go服务构建并推送docker镜像。如果你想在本地环境中手动测试服务,你可以做以下工作。然后你可以使用http://0.0.0.0:8888 来测试学生服务端点。

monorepo$ docker build -t you/monorepo-go:latest -f infra/prod/docker/go/Dockerfile go/
monorepo$ docker run --name monorepo-student -d -p 8888:8000 you/monorepo-go:latest student

$ docker ps
IMAGE                    COMMAND                      PORTS                                       NAMES
you/monorepo-go:latest   "./monorepo --svc student"   0.0.0.0:8888->8000/tcp, :::8888->8000/tcp   monorepo-student

部署到Kubernetes

首先运行$ kubectl create namespace prod 命令来创建命名空间,然后运行monorepo/infra/prod$ make k8s-deploy-go 命令来部署go服务。你应该看到如下所示的资源:

$ kubectl -n prod get all
NAME                               READY   STATUS    RESTARTS   AGE
pod/dep-account-6db8bb9c68-v5dr6   1/1     Running   0          18s
pod/dep-exam-54b78448d-nd6js       1/1     Running   0          17s
pod/dep-student-6fc98cc9dd-qpxwc   1/1     Running   0          17s

NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/svc-account   ClusterIP   10.108.90.79             8000/TCP   18s
service/svc-exam      ClusterIP   10.108.226.180           8000/TCP   18s
service/svc-student   ClusterIP   10.109.116.75            8000/TCP   17s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dep-account   1/1     1            1           18s
deployment.apps/dep-exam      1/1     1            1           17s
deployment.apps/dep-student   1/1     1            1           17s

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/dep-account-6db8bb9c68   1         1         1       18s
replicaset.apps/dep-exam-54b78448d       1         1         1       17s
replicaset.apps/dep-student-6fc98cc9dd   1         1         1       17s

测试

首先,用下面的命令使你的服务可以从你的本地环境访问:

$ kubectl -n prod port-forward service/svc-student 8001:8000
Forwarding from 127.0.0.1:8001 -> 8000
Forwarding from [::1]:8001 -> 8000

$ kubectl -n prod port-forward service/svc-exam 8002:8000
Forwarding from 127.0.0.1:8002 -> 8000
Forwarding from [::1]:8002 -> 8000

$ kubectl -n prod port-forward service/svc-account 8003:8000
Forwarding from 127.0.0.1:8003 -> 8000
Forwarding from [::1]:8003 -> 8000

现在你可以使用下面的端点,并观察pod日志进行确认:

http://127.0.0.1:8001/pay-debt (external for you to consume)
http://127.0.0.1:8001/publish-results (internal for exam service to consume)

# Exam
http://127.0.0.1:8002/publish-results (external for you to consume)
http://127.0.0.1:8002/unlock-results (internal for account service to consume)

# Account
http://127.0.0.1:8003/pay-debt (internal for student service to consume)