Bu, birden çok dilin aynı depo altında tutulduğu bir monorepo tasarımının sadece bir örneğidir. Her dilin kendi klasörü vardır. Tüm dilleri aynı depoda tutmak yerine monorepoyu dil bazında da bölebilirsiniz. Her şey size ve ihtiyaçlarınıza bağlıdır.


Burada size bir fikir vermesi için Go dili ile çalışacağız. Üç servis olacak ve her biri birbiriyle konuşabilecek. Test söz konusu olduğunda, istediğimiz servisi çalıştırmak için ismini verebileceğimiz bir Docker imajımız olacak. Daha sonra bu imajı, üç servisi de çalıştırmak için Kubernetes'e dağıtacağız. Tüm hizmetlerin dışarıdan ulaşabileceğimiz uç noktaları vardır. Ayrıca, servisten servise iletişim için bazı uç noktalar olacaktır.


Aklınızda bulundurmanız gereken şey, bunun tamamlanmamış bir örnek olduğu göz önüne alındığında, bazı gereksiz tekrarlar ve daha az değerli parçalar olacağıdır. Ana amaç, size bir fikir vermektir!


Yapı


Açıklama


├── 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

Örnek


├── .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

Dosyalar


.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 file


Ben burada üç yaml dosyasını da eklemeyeğim ama siz ekleyin. Bunu yaptığınızda, account kelimesini student ve exam ile değiştirmeniz yeterlidir.


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)
}
}

go.mod


module monorepo

go 1.17

Docker push


Go servislerini ve Docker imajını oluşturmak için monorepo/infra/prod$ make docker-push-go komutunu çalıştırın. Servisleri yerel ortamda manuel olarak test etmek istiyorsanız aşağıdakileri yapabilirsiniz. Daha sonra student hizmetini test etmek için http://0.0.0.0:8888 adresini kullanabilirsiniz.


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'e iletim


Namaspace bilgisini yaratmak için öncelikle $ kubectl create namespace prod komutunu çalıştırın ve daha sonra monorepo/infra/prod$ make k8s-deploy-go komutu ile servisin iletimini gerçekleştirin. Kaynakları aşağıda gösterildiği gibi görmelisiniz.


$ 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

Test


Öncelikle aşağıdaki komutla servislerinizi yerel ortamınızdan erişilebilir hale getirin.


$ 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

Aşağıdaki uç noktaları kullanabilir ve emin olmak için pod günlüklerini izleyebilirsiniz.


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)