├── .gitignore ├── README ├── chapter01-basic-knowledges ├── 1.1-basic-golang │ ├── 01-install-golang │ │ └── README │ ├── 02-first-program │ │ ├── README │ │ ├── formatter │ │ │ └── index.go │ │ └── main.go │ ├── 03-variable-types │ │ ├── README │ │ └── main.go │ ├── 04-if-and-loop │ │ ├── README │ │ └── main.go │ ├── 05-interface-struct │ │ ├── README │ │ └── main.go │ └── 06-go-routine │ │ ├── README │ │ └── main.go ├── 1.2-basic-docker │ ├── 01-install-docker │ │ └── README │ ├── 02-run-nginx-with-docker │ │ ├── README │ │ └── docker-compose.yml │ ├── 03-build-docker-image │ │ ├── Dockerfile │ │ ├── README │ │ ├── docker-compose.yml │ │ └── html │ │ │ └── index.html │ ├── 04-push-docker-image │ │ └── README │ ├── 05-run-docker-compose │ │ ├── README │ │ ├── docker-compose.yml │ │ └── dockers │ │ │ └── lb │ │ │ ├── logs │ │ │ ├── access.log │ │ │ └── error.log │ │ │ └── nginx │ │ │ └── conf │ │ │ ├── bitnami │ │ │ └── certs │ │ │ │ ├── server.crt │ │ │ │ └── server.key │ │ │ ├── fastcgi.conf │ │ │ ├── fastcgi.conf.default │ │ │ ├── fastcgi_params │ │ │ ├── fastcgi_params.default │ │ │ ├── koi-utf │ │ │ ├── koi-win │ │ │ ├── mime.types │ │ │ ├── mime.types.default │ │ │ ├── nginx.conf │ │ │ ├── nginx.conf.default │ │ │ ├── scgi_params │ │ │ ├── scgi_params.default │ │ │ ├── uwsgi_params │ │ │ ├── uwsgi_params.default │ │ │ ├── vhosts │ │ │ └── app.conf │ │ │ └── win-utf │ └── extra-image-nginx-1.12 │ │ ├── Dockerfile │ │ └── rootfs │ │ ├── app-entrypoint.sh │ │ └── nginx-inputs.json ├── 1.3-basic-kubernetes │ ├── 01-install-k8s │ │ └── README │ └── 02-run-nginx-with-k8s │ │ ├── 00-namespace.yml │ │ ├── 01-deployment.yml │ │ ├── 02-service.yml │ │ ├── 03-ingress.yml │ │ └── README ├── 1.4-basic-redis │ ├── 01-run-redis │ │ ├── 00-namespace.yml │ │ ├── 01-deployment.yml │ │ ├── 02-service.yml │ │ ├── 03-client-util.yml │ │ └── README │ ├── 02-use-redis-cli │ │ └── README │ └── extra-redis-image │ │ ├── client-util-1.0 │ │ └── Dockerfile │ │ └── redis-5.0 │ │ ├── Dockerfile │ │ └── rootfs │ │ ├── entrypoint.sh │ │ ├── libredis.sh │ │ ├── postunpack.sh │ │ ├── redis-inputs.json │ │ ├── run.sh │ │ └── setup.sh └── 1.5-basic-kafka │ ├── 01-run-kafka │ ├── 00-namespace.yml │ ├── 01-deployment-zk.yml │ ├── 02-service-zk.yml │ ├── 03-deployment-kfk.yml │ ├── 04-service-kfk.yml │ ├── 05-client-util.yml │ └── README │ ├── 02-play-consumer-producer │ └── README │ ├── 03-play-consumer-group │ └── README │ └── extra-docker-images │ ├── kfk-2.0 │ ├── Dockerfile │ └── rootfs │ │ ├── app-entrypoint.sh │ │ ├── init.sh │ │ ├── kafka-inputs.json │ │ └── run.sh │ └── zk-3.0 │ ├── Dockerfile │ └── rootfs │ ├── app-entrypoint.sh │ ├── init.sh │ ├── run.sh │ └── zookeeper-inputs.json ├── chapter02-microservices ├── 2.2-microservices-types │ ├── 01-http-service │ │ ├── README │ │ ├── context.go │ │ ├── context_http.go │ │ ├── main.go │ │ └── microservice.go │ ├── 02-consumer-service │ │ ├── README │ │ ├── context.go │ │ ├── context_consumer.go │ │ ├── docker-compose.yml │ │ ├── main.go │ │ ├── microservice.go │ │ ├── producer.go │ │ └── util.go │ ├── 03-batch-consumer-service │ │ ├── README │ │ ├── batch_event.go │ │ ├── context.go │ │ ├── context_consumer_batch.go │ │ ├── docker-compose.yml │ │ ├── main.go │ │ ├── microservice.go │ │ ├── producer.go │ │ └── util.go │ ├── 04-scheduler-service │ │ ├── README │ │ ├── context.go │ │ ├── context_scheduler.go │ │ ├── main.go │ │ └── microservice.go │ ├── 05-asynctask-service │ │ ├── README │ │ ├── cacher.go │ │ ├── context.go │ │ ├── context_asynctask.go │ │ ├── context_consumer.go │ │ ├── context_http.go │ │ ├── docker-compose.yml │ │ ├── main.go │ │ ├── microservice.go │ │ ├── mq.go │ │ ├── producer.go │ │ └── util.go │ └── 06-paralleltask-service │ │ ├── README │ │ ├── cacher.go │ │ ├── context.go │ │ ├── context_asynctask.go │ │ ├── context_consumer.go │ │ ├── context_http.go │ │ ├── context_ptask.go │ │ ├── docker-compose.yml │ │ ├── main.go │ │ ├── microservice.go │ │ ├── mq.go │ │ ├── producer.go │ │ └── util.go └── 2.3-service-startup-teardown │ ├── README │ ├── batch_event.go │ ├── cacher.go │ ├── context.go │ ├── context_asynctask.go │ ├── context_consumer.go │ ├── context_consumer_batch.go │ ├── context_http.go │ ├── context_ptask.go │ ├── context_scheduler.go │ ├── docker-compose.yml │ ├── main.go │ ├── microservice.go │ ├── mq.go │ ├── producer.go │ └── util.go ├── chapter03-deploy-scale-services ├── 3.1-deploy-services │ ├── Dockerfile │ ├── README │ ├── batch_event.go │ ├── cacher.go │ ├── context.go │ ├── context_asynctask.go │ ├── context_consumer.go │ ├── context_consumer_batch.go │ ├── context_http.go │ ├── context_ptask.go │ ├── context_scheduler.go │ ├── deploy.sh │ ├── entrypoint.sh │ ├── k8s │ │ ├── 00-namespace.yml │ │ ├── 01-register-api.yml │ │ └── 02-ingress.yml │ ├── main.go │ ├── microservice.go │ ├── mq.go │ ├── producer.go │ └── util.go ├── 3.2-healthcheck-readiness │ ├── Dockerfile │ ├── README │ ├── batch_event.go │ ├── cacher.go │ ├── context.go │ ├── context_asynctask.go │ ├── context_consumer.go │ ├── context_consumer_batch.go │ ├── context_http.go │ ├── context_ptask.go │ ├── context_scheduler.go │ ├── deploy.sh │ ├── entrypoint.sh │ ├── k8s │ │ ├── 00-databases │ │ │ ├── 00-namespace.yml │ │ │ └── 01-redis.yml │ │ └── 01-application │ │ │ ├── 02-register-api.yml │ │ │ └── 03-ingress.yml │ ├── main.go │ ├── microservice.go │ ├── microservice_asynctask.go │ ├── microservice_batch_consumer.go │ ├── microservice_consumer.go │ ├── microservice_http.go │ ├── microservice_liveness.go │ ├── microservice_paralleltask.go │ ├── microservice_scheduler.go │ ├── mq.go │ ├── producer.go │ └── util.go └── 3.3-scale-services │ ├── Dockerfile │ ├── README │ ├── batch_event.go │ ├── cacher.go │ ├── config.go │ ├── context.go │ ├── context_asynctask.go │ ├── context_consumer.go │ ├── context_consumer_batch.go │ ├── context_http.go │ ├── context_ptask.go │ ├── context_scheduler.go │ ├── deploy.sh │ ├── entrypoint.sh │ ├── k8s │ ├── 00-databases │ │ ├── 00-namespace.yml │ │ ├── 01-redis.yml │ │ ├── 02-zk.yml │ │ ├── 03-kfk.yml │ │ └── 04-client-util.yml │ └── 01-application │ │ ├── 05-register-api.yml │ │ ├── 06-mail-consumer.yml │ │ └── 07-ingress.yml │ ├── main.go │ ├── microservice.go │ ├── microservice_asyncttask.go │ ├── microservice_batch_consumer.go │ ├── microservice_consumer.go │ ├── microservice_http.go │ ├── microservice_liveness.go │ ├── microservice_paralleltask.go │ ├── microservice_scheduler.go │ ├── mq.go │ ├── producer.go │ └── util.go └── chapter04-implement-reallife-app └── 4.2-tcir-application ├── Dockerfile ├── README ├── batch_event.go ├── cacher.go ├── config.go ├── context.go ├── context_asynctask.go ├── context_consumer.go ├── context_consumer_batch.go ├── context_http.go ├── context_ptask.go ├── context_scheduler.go ├── deploy.sh ├── entrypoint.sh ├── k8s ├── 00-databases │ ├── 00-namespace.yml │ ├── 01-redis.yml │ ├── 02-zk.yml │ ├── 03-kfk.yml │ └── 04-client-util.yml └── 01-application │ ├── 05-register-api.yml │ ├── 06-mail-consumer.yml │ ├── 07-batch-scheduler.yml │ ├── 08-batch-ptask-api.yml │ ├── 09-batch-ptask-worker.yml │ ├── 10-external-mock-api.yml │ └── 11-ingress.yml ├── main.go ├── microservice.go ├── microservice_asynctask.go ├── microservice_batch_consumer.go ├── microservice_consumer.go ├── microservice_http.go ├── microservice_liveness.go ├── microservice_paralleltask.go ├── microservice_scheduler.go ├── models.go ├── mq.go ├── producer.go ├── requester.go └── util.go /.gitignore: -------------------------------------------------------------------------------- 1 | go.mod 2 | go.sum 3 | dockers 4 | main 5 | vendor 6 | .DS_Store 7 | access.log 8 | error.log -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ## The workshop content for Automation and scalable technology using Golang and K8S workshop 2 | 3 | If you are interested in the workshop, register and join here https://bit.ly/automationtech1-web 4 | 5 | ### Workshop Outline 6 | 7 | Chapter 1. Basic Knowledges 8 | Lesson 1.1 Basic Golang 9 | Workshop 1 - Install Golang 10 | Workshop 2 - First Program 11 | Workshop 3 - Variable and Types 12 | Workshop 4 - Conditional and Loop 13 | Workshop 5 - Interface and Struct 14 | Workshop 6 - Go routine and Channel 15 | 16 | Lesson 1.2 Basic Docker 17 | Workshop 7 - Install Docker 18 | Workshop 8 - Run Program with Docker 19 | Workshop 9 - Build Docker image 20 | Workshop 10 - Push Docker Image 21 | Workshop 11 - Run Docker compose 22 | 23 | Lesson 1.3 Basic Kubernetes 24 | Workshop 12 - Install Kubernetes 25 | Workshop 13 - Run Program with Kubernetes 26 | 27 | 28 | Lesson 1.4 Basic Redis 29 | Workshop 14 - Run Redis in Kubernetes 30 | Workshop 15 - Use redis-cli 31 | 32 | 33 | Lesson 1.5 Basic Kafka 34 | Workshop 16 - Run Kafka in Kubernetes 35 | Workshop 17 - Play with Consumer and Producer 36 | Workshop 18 - Play with Consumer Group 37 | 38 | Chapter 2. Microservices 39 | Lesson 2.1 What is Services 40 | 41 | 42 | Lesson 2.2 Service Types 43 | Workshop 19 - Coding on HTTP Service 44 | Workshop 20 - Coding on Consumer Service 45 | Workshop 21 - Coding on Batch Consumer Service 46 | Workshop 22 - Coding on Scheduler Service 47 | Workshop 23 - Coding on Async Task Service 48 | Workshop 24 - Coding on Parallel Task Service 49 | 50 | 51 | Lesson 2.3 Service Startup and Teardown 52 | Workshop 25 - Composing service & start service & teardown service 53 | 54 | Chapter 3. Service Deployment and Scale 55 | Lesson 3.1 Service Deployment 56 | Workshop 26 - Deploy Service in Kubernetes 57 | Workshop 27 - Setup Service Health check 58 | Workshop 28 - Scale Service 59 | 60 | Chapter 4. Implement Real-life high load application 61 | Lesson 4.1 Thai Citizen ID Card Register (TCIR) Application architecture 62 | 63 | 64 | Lesson 4.2 Thai Citizen ID Card Register (TCIR) Application 65 | Workshop 29 - Coding, Building and Deploy TCIR Application in K8S 66 | Workshop 30 - Scale TCIR Application in K8S 67 | 68 | By Chaiyapong Lapliengtrakul 69 | ไชยพงศ์ ลาภเลี้ยงตระกูล 70 | All right reserved -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.1-basic-golang/01-install-golang/README: -------------------------------------------------------------------------------- 1 | Install Golang 2 | 3 | 1. Install Golang by Google "install golang" 4 | 5 | 2. Download installer according to your platform (Mac, Windows, Ubuntu, etc) 6 | 7 | 3. After install open terminal and run command 8 | $ go version 9 | go version go1.15.7 darwin/amd64 -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.1-basic-golang/02-first-program/README: -------------------------------------------------------------------------------- 1 | First Program 2 | 3 | 0. git clone git@github.com:3dsinteractive/automation-scalable-technology-with-golang-and-k8s-part-1.git 4 | Directory structure will follow each lesson respectively 5 | 6 | 1. Download and install VSCode (Use Google to search for "install vscode") 7 | 8 | 2. Open vscode at directory chapter01-basic-knowledges/1.1-basic-golang/02-first-program 9 | 10 | 3. Open terminal in VSCode using (Ctrl + ~) 11 | 12 | 4. Run command 13 | $ go mod init automationworkshop/main 14 | go: creating new go.mod: module automationworkshop/main 15 | 16 | 5. Run command 17 | $ go run main.go 18 | My First Program 19 | This is from MyPrintln 20 | 21 | 6. Program are described by the comment in sourcecode 22 | 23 | 24 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.1-basic-golang/02-first-program/formatter/index.go: -------------------------------------------------------------------------------- 1 | package formatter 2 | 3 | import "fmt" 4 | 5 | // MyPrintln will print input string 6 | func MyPrintln(input string) { 7 | myPrintln(input) 8 | } 9 | 10 | // myPrintln will not export 11 | func myPrintln(input string) { 12 | fmt.Println(input) 13 | } 14 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.1-basic-golang/02-first-program/main.go: -------------------------------------------------------------------------------- 1 | // 1. Package name must be main for main package 2 | package main 3 | 4 | // 2. Import section is the list of package dependency 5 | import ( 6 | "automationworkshop/main/formatter" 7 | "fmt" 8 | ) 9 | 10 | // 3. main() is the function where application start 11 | func main() { 12 | fmt.Println("My First Program") 13 | 14 | formatter.MyPrintln("This is from MyPrintln") 15 | // This function is not export 16 | // formatter.myPrintln("This is from MyPrintln") 17 | } 18 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.1-basic-golang/03-variable-types/README: -------------------------------------------------------------------------------- 1 | Variables and Types 2 | 3 | 1. Open vscode at directory chapter01-basic-knowledges/1.1-basic-golang/03-variable-types 4 | 5 | 2. Open terminal in VSCode using (Shortcut Ctrl + ~) 6 | 7 | 3. Run command 8 | $ go mod init automationworkshop/main 9 | go: creating new go.mod: module automationworkshop/main 10 | 11 | 4. Run command 12 | $ go run main.go 13 | string = This variable type string 14 | int = 305 15 | bool = true 16 | --- 17 | interface = This variable type string 18 | interface = 305 19 | interface = true 20 | --- 21 | map = map[citizen_id:1234 firstname:Chaiyapong lastname:Lapliengtrakul] 22 | map JSON = {"citizen_id":"1234","firstname":"Chaiyapong","lastname":"Lapliengtrakul"} 23 | No Gender specify 24 | --- 25 | slice = [item 1 item 2 item 3] 26 | slice JSON = ["item 1","item 2","item 3"] 27 | --- 28 | Citizen = &{Chaiyapong Lapliengtrakul 1234} 29 | Citizen JSON = {"firstname":"Chaiyapong","lastname":"Lapliengtrakul","citizen_id":"1234"} 30 | --- 31 | theMap is nil 32 | theCitizen is nil 33 | --- 34 | Gender is Unspecify 35 | Gender = UNSPECIFY 36 | --- 37 | 38 | 5. Sourcecode are described by the comments -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.1-basic-golang/04-if-and-loop/README: -------------------------------------------------------------------------------- 1 | If and Loop 2 | 3 | 1. Open vscode in chapter01-basic-knowledges/1.1-basic-golang/04-if-and-loop 4 | 5 | 2. Run command 6 | $ go mod init automationworkshop/main 7 | go: creating new go.mod: module automationworkshop/main 8 | 9 | 3. Run Program 10 | $ go run main.go 11 | CitizenID 1234, you are logged in 12 | --- 13 | You are Female 14 | --- 15 | Loop i=0 16 | Loop i=1 17 | Loop i=2 18 | Loop i=3 19 | Loop i=4 20 | Loop i=5 21 | Loop i=6 22 | Loop i=7 23 | Loop i=8 24 | Loop i=9 25 | --- 26 | Country 0=Thailand 27 | Country 1=Japan 28 | Country 2=China 29 | Country 3=Korea 30 | Country 4=Vietnam 31 | --- 32 | For i=1 33 | For i=2 34 | For i=3 35 | For i=4 36 | For i=5 37 | For i=6 38 | For i=7 39 | For i=8 40 | For i=9 41 | For i=10 42 | --- 43 | For n=1 44 | For n=2 45 | For n=3 46 | For n=4 47 | For n=5 48 | For n=6 49 | For n=7 50 | For n=8 51 | For n=9 52 | For n=10 53 | For n=11 54 | 55 | 4. Explain are in the source code comment -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.1-basic-golang/05-interface-struct/README: -------------------------------------------------------------------------------- 1 | Interface and struct 2 | 3 | 1. Open vscode at chapter01-basic-knowledges/1.1-basic-golang/05-interface-struct 4 | 5 | 2. Run command 6 | $ go mod init automationworkshop/main 7 | 8 | 3. Run program 9 | $ go run main.go 10 | Successfully create citizen card for ID=1122334455 11 | --- 12 | 13 | 4. Code explain in comments -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.1-basic-golang/06-go-routine/README: -------------------------------------------------------------------------------- 1 | Go routine and Go Channel 2 | 3 | 0. See what is Go Routine and Go Channel in Slides 4 | 5 | 1. Open vscode at chapter01-basic-knowledges/1.1-basic-golang/06-go-routine 6 | 7 | 2. Run command 8 | $ go mod init automationworkshop/main 9 | 10 | 3. Run program 11 | $ go run main.go 12 | 13 | 4. Code are explained in comments. -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/01-install-docker/README: -------------------------------------------------------------------------------- 1 | Install Docker 2 | 3 | 1. Install docker from https://docs.docker.com/get-docker/ 4 | 5 | 2. Double click Docker icon in Application directory, wait until docker is running in menu bar 6 | 7 | 3. After install open terminal and run command 8 | $ docker version 9 | Client: Docker Engine - Community 10 | Version: 20.10.2 11 | API version: 1.41 12 | Go version: go1.13.15 13 | Git commit: 2291f61 14 | Built: Mon Dec 28 16:12:42 2020 15 | OS/Arch: darwin/amd64 16 | Context: default 17 | Experimental: true 18 | 19 | Server: Docker Engine - Community 20 | Engine: 21 | Version: 20.10.2 22 | API version: 1.41 (minimum version 1.12) 23 | Go version: go1.13.15 24 | Git commit: 8891c58 25 | Built: Mon Dec 28 16:15:28 2020 26 | OS/Arch: linux/amd64 27 | Experimental: false 28 | containerd: 29 | Version: 1.4.3 30 | GitCommit: 269548fa27e0089a8b8278fc4fc781d7f65a939b 31 | runc: 32 | Version: 1.0.0-rc92 33 | GitCommit: ff819c7e9184c13b7c2607fe6c30ae19403a7aff 34 | docker-init: 35 | Version: 0.19.0 36 | GitCommit: de40ad0 -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/02-run-nginx-with-docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | nginx: 4 | image: 3dsinteractive/nginx:1.12 5 | ports: 6 | - 8080:8080 7 | - 8443:8443 8 | restart: always 9 | volumes: 10 | - ./dockers/nginx/nginx:/bitnami/nginx -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/03-build-docker-image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 3dsinteractive/nginx:1.12 2 | 3 | COPY ./html /app 4 | 5 | USER 1001 6 | 7 | EXPOSE 8080 -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/03-build-docker-image/README: -------------------------------------------------------------------------------- 1 | Build docker image 2 | 3 | 1. Open vscode at chapter01-basic-knowledges/1.2-basic-docker/03-build-docker-image 4 | 5 | 2. Run command 6 | $ docker build -t 3dsinteractive/mynginx:1.0 . 7 | 8 | 3. Run command 9 | $ docker-compose up -d 10 | Creating network "03-build-docker-image_default" with the default driver 11 | Creating 03-build-docker-image_nginx_1 ... done 12 | 13 | 4. Run command 14 | $ curl -X GET localhost:8080/index.html 15 | 16 |
Hello World
17 | 18 |

Hello world

19 | 20 | 21 | 22 | 5. Explain Dockerfile 23 | 24 | 25 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/03-build-docker-image/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | nginx: 4 | image: 3dsinteractive/mynginx:1.0 5 | ports: 6 | - 8080:8080 -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/03-build-docker-image/html/index.html: -------------------------------------------------------------------------------- 1 | 2 |
Hello World
3 | 4 |

Hello world

5 | 6 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/04-push-docker-image/README: -------------------------------------------------------------------------------- 1 | Push docker image 2 | 3 | 0. Look at slides 1.2 Basic Docker (Docker images and containers) 4 | 5 | 1. Register for https://hub.docker.com 6 | 7 | 2. Create public repository call [your-repository-name]/mynginx:1.0 8 | 9 | 3. docker login 10 | [username] : [your-repository-name] 11 | [password] : xxxxxxxxx 12 | 13 | 4. Run command (Change [your-repository-name] to your registered docker name) 14 | $ docker push [your-repository-name]/mynginx:1.0 -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/README: -------------------------------------------------------------------------------- 1 | Run Docker compose 2 | 3 | 1. Open vscode at chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose 4 | 5 | 2. Run command 6 | $ docker-compose up -d 7 | $ docker ps 8 | 9 | 3. Run command 10 | $ curl -X GET "localhost:8080/index.html" 11 | 12 | 4. Explain section in docker-compose.yml 13 | 14 | 5. Run command 15 | $ docker-compose down -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | lb: 4 | image: 3dsinteractive/nginx:1.12 5 | depends_on: 6 | - mynginx1 7 | - mynginx2 8 | ports: 9 | - 8080:8080 10 | - 8443:8443 11 | restart: always 12 | volumes: 13 | - ./dockers/lb/logs:/opt/bitnami/nginx/logs 14 | - ./dockers/lb/nginx:/bitnami/nginx 15 | mynginx1: 16 | image: 3dsinteractive/mynginx:1.0 17 | mynginx2: 18 | image: 3dsinteractive/mynginx:1.0 19 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/logs/access.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3dsinteractive/automation-scalable-technology-with-golang-and-k8s-part-1/2ce397b7fb04caf3f2cf5516a7e6c4fab2d45a70/chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/logs/access.log -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/logs/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3dsinteractive/automation-scalable-technology-with-golang-and-k8s-part-1/2ce397b7fb04caf3f2cf5516a7e6c4fab2d45a70/chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/logs/error.log -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/bitnami/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICqDCCAZACCQCz8T3726LYsjANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtl 3 | eGFtcGxlLmNvbTAeFw0xMjExMTQxMTE4MjdaFw0yMjExMTIxMTE4MjdaMBYxFDAS 4 | BgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC 5 | AQEA5NHl5TfZtO6zugau2tp5mWIcQYJhuwKTmYeXDLYAGJpoD2SixwPL5c8glneI 6 | Rz1N2EQIZVeaWGbS0FLFlPdOkCkplpW9isYVC4XqKrk5b4HW4+YC+Cup0k+Kd4NM 7 | eZOTUvWr5N6dIpdibkVumBc/pao8VtdwywlCL/PwGRsQtkXrRICzdtRa3MXqTmEF 8 | foyVCGgBRtronlB9x4Plfb8Psk4GrPkjrWYgO8peKrl0O5+F+sYg7Gj95zCH73BQ 9 | ANzCVNrgD9fs9cyx3ru9CUdEoIxAAJwQFkjm7xr6xqhIlSgnQ7B0uOSTNRcXY6rw 10 | s+PxGneec/kRPRgzjC/QHY6n8QIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBbyMqF 11 | RDsX8zX1EW5qA8AQ8Jb2XqWrVeSO8blMV3WagJ2airMm3+c/82FCwsd/cZ08UXhA 12 | /Kou0gi/F16tV26PiiUdp590Qao3d8H2qxc1rzzULimZPgxH4iA4vRyMHtyZN6h4 13 | 7Fdn7O9xNMPu8siOz8rrzsEdEX5URbOMkDLCZsbTIUWVv2XmqrR0K10d5VuLWeLi 14 | r+4G6c6jpa244WmqT9ClqceJ12G1Wnmezy7ybiW0l5M2iuIKFEiRP5Hj0J15o1I2 15 | pXAbKysAdWRHsJSQOtcgO8Vh9k0wo3tKg4HDp1hbrEzoGzOv92Vjg3lG8X+hzbMJ 16 | MQURotHkD4Gk57wL 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/bitnami/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA5NHl5TfZtO6zugau2tp5mWIcQYJhuwKTmYeXDLYAGJpoD2Si 3 | xwPL5c8glneIRz1N2EQIZVeaWGbS0FLFlPdOkCkplpW9isYVC4XqKrk5b4HW4+YC 4 | +Cup0k+Kd4NMeZOTUvWr5N6dIpdibkVumBc/pao8VtdwywlCL/PwGRsQtkXrRICz 5 | dtRa3MXqTmEFfoyVCGgBRtronlB9x4Plfb8Psk4GrPkjrWYgO8peKrl0O5+F+sYg 6 | 7Gj95zCH73BQANzCVNrgD9fs9cyx3ru9CUdEoIxAAJwQFkjm7xr6xqhIlSgnQ7B0 7 | uOSTNRcXY6rws+PxGneec/kRPRgzjC/QHY6n8QIDAQABAoIBACo3G131tuGtpFTu 8 | xLW11vdYZXQklNlGuWp63IBI162yVv54B5wF9Ek6tH1uIiNaiREcRBxGVEB4/+3V 9 | R4SbN9Ba98RDbgu7TcipdTFaqOEMqFO1bNjSXWtip14zSBmqA2Ur1AHOnFj0awGD 10 | J8tBhsmOpcEz0Ch1VdO5ApPvLV8jH9wQiMI/Q6yYQMtmzTMCUMYdMqe+LOziIOzL 11 | oqN/WXnKL5E5TiO1bIxSpWPbT+IVn1c3/PShmvmRrLWsFUQlkwXJKMYZPO+rCCfe 12 | b+Q9lMLMnj+vOnM3z16WC3aiiJGCZjVTvQ+x22YrBTRPxZmHO2eZ4H/cUQM7Y/tw 13 | I7RjEM0CgYEA9Kxt1t8bWonzBii3P0rwyx0IECvg63k+pp4BpxpeWQKL7NVdSzk3 14 | AyJVcNjUoZgi2kVPdxzZGLrnZfuZ691xQB3oZF0LwBzQ4GFHkTRCB0s8ZA5lcJaI 15 | 9pBu91bhz2VOZSTeQWpdMMURjXVyTXZInU1mwzmjVOIAYmO33shH9gcCgYEA72mX 16 | UoIrFPLkOTSZOb7UbjYH01vf6ThQiYCEWg7mD3CbY7n9oobIcQMzNnt7xN4wOl/V 17 | eKfZ7G56q8enfqm45Dyo9aCBCENVzmwO8wLe5UnvJBNL20KjvtwG8w5A6UZQzC7p 18 | 3QS+U2zxVQNEeaE6a8Wrq2d1PlhVAHYw8odgNEcCgYBN38+58xrmrz99d1oTuAt5 19 | 6kyVsRGOgPGS4HmQMRFUbT4R7DscZSKASd4945WRtTVqmWLYe4MRnvNlfzYXX0zb 20 | ZmmAAClsRP+qWuwHaEWXwrd+9SIOOqtvJrta1/lZJFpWUOy4j10H18Flb7sosnwc 21 | LPWHL4Iv0xriNfDg5Iga4wKBgQDLJBU59SkJBW+Q+oho7vrg6QeK15IOGbJ8eYfT 22 | woCC6VFwNQh5N1QsUELMH8rNKJpTba18SzAl5ThBOY9tciVnw/C5Og9CK6BLHnUw 23 | zWbDtxAq1BSxXsIB2EAtTBLX3MoB9myJFNVJhE7hi3w2mA8yEu+u6IIa/Ghjk+XE 24 | ZAnFUQKBgQDjMinRZrK5wA09jcetI+dNiLnKHoQG6OaXDDsNCatex0O2F36BvVXE 25 | P78qDz/i5aBMWsLx6VDvWJAkBIpZoNS5UsOn17tFaocGUSkcm48bs8Dn6VvsE8Bd 26 | XMPAHyKuILlKYifBvNq5T22KhqKX7yGmk/AeOOiKr2KeMnh27JYrCA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/fastcgi.conf: -------------------------------------------------------------------------------- 1 | 2 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 3 | fastcgi_param QUERY_STRING $query_string; 4 | fastcgi_param REQUEST_METHOD $request_method; 5 | fastcgi_param CONTENT_TYPE $content_type; 6 | fastcgi_param CONTENT_LENGTH $content_length; 7 | 8 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 9 | fastcgi_param REQUEST_URI $request_uri; 10 | fastcgi_param DOCUMENT_URI $document_uri; 11 | fastcgi_param DOCUMENT_ROOT $document_root; 12 | fastcgi_param SERVER_PROTOCOL $server_protocol; 13 | fastcgi_param REQUEST_SCHEME $scheme; 14 | fastcgi_param HTTPS $https if_not_empty; 15 | 16 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; 17 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 18 | 19 | fastcgi_param REMOTE_ADDR $remote_addr; 20 | fastcgi_param REMOTE_PORT $remote_port; 21 | fastcgi_param SERVER_ADDR $server_addr; 22 | fastcgi_param SERVER_PORT $server_port; 23 | fastcgi_param SERVER_NAME $server_name; 24 | 25 | # PHP only, required if PHP was built with --enable-force-cgi-redirect 26 | fastcgi_param REDIRECT_STATUS 200; 27 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/fastcgi.conf.default: -------------------------------------------------------------------------------- 1 | 2 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 3 | fastcgi_param QUERY_STRING $query_string; 4 | fastcgi_param REQUEST_METHOD $request_method; 5 | fastcgi_param CONTENT_TYPE $content_type; 6 | fastcgi_param CONTENT_LENGTH $content_length; 7 | 8 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 9 | fastcgi_param REQUEST_URI $request_uri; 10 | fastcgi_param DOCUMENT_URI $document_uri; 11 | fastcgi_param DOCUMENT_ROOT $document_root; 12 | fastcgi_param SERVER_PROTOCOL $server_protocol; 13 | fastcgi_param REQUEST_SCHEME $scheme; 14 | fastcgi_param HTTPS $https if_not_empty; 15 | 16 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; 17 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 18 | 19 | fastcgi_param REMOTE_ADDR $remote_addr; 20 | fastcgi_param REMOTE_PORT $remote_port; 21 | fastcgi_param SERVER_ADDR $server_addr; 22 | fastcgi_param SERVER_PORT $server_port; 23 | fastcgi_param SERVER_NAME $server_name; 24 | 25 | # PHP only, required if PHP was built with --enable-force-cgi-redirect 26 | fastcgi_param REDIRECT_STATUS 200; 27 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/fastcgi_params: -------------------------------------------------------------------------------- 1 | 2 | fastcgi_param QUERY_STRING $query_string; 3 | fastcgi_param REQUEST_METHOD $request_method; 4 | fastcgi_param CONTENT_TYPE $content_type; 5 | fastcgi_param CONTENT_LENGTH $content_length; 6 | 7 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 8 | fastcgi_param REQUEST_URI $request_uri; 9 | fastcgi_param DOCUMENT_URI $document_uri; 10 | fastcgi_param DOCUMENT_ROOT $document_root; 11 | fastcgi_param SERVER_PROTOCOL $server_protocol; 12 | fastcgi_param REQUEST_SCHEME $scheme; 13 | fastcgi_param HTTPS $https if_not_empty; 14 | 15 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; 16 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 17 | 18 | fastcgi_param REMOTE_ADDR $remote_addr; 19 | fastcgi_param REMOTE_PORT $remote_port; 20 | fastcgi_param SERVER_ADDR $server_addr; 21 | fastcgi_param SERVER_PORT $server_port; 22 | fastcgi_param SERVER_NAME $server_name; 23 | 24 | # PHP only, required if PHP was built with --enable-force-cgi-redirect 25 | fastcgi_param REDIRECT_STATUS 200; 26 | fastcgi_param HTTP_PROXY ""; -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/fastcgi_params.default: -------------------------------------------------------------------------------- 1 | 2 | fastcgi_param QUERY_STRING $query_string; 3 | fastcgi_param REQUEST_METHOD $request_method; 4 | fastcgi_param CONTENT_TYPE $content_type; 5 | fastcgi_param CONTENT_LENGTH $content_length; 6 | 7 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 8 | fastcgi_param REQUEST_URI $request_uri; 9 | fastcgi_param DOCUMENT_URI $document_uri; 10 | fastcgi_param DOCUMENT_ROOT $document_root; 11 | fastcgi_param SERVER_PROTOCOL $server_protocol; 12 | fastcgi_param REQUEST_SCHEME $scheme; 13 | fastcgi_param HTTPS $https if_not_empty; 14 | 15 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; 16 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 17 | 18 | fastcgi_param REMOTE_ADDR $remote_addr; 19 | fastcgi_param REMOTE_PORT $remote_port; 20 | fastcgi_param SERVER_ADDR $server_addr; 21 | fastcgi_param SERVER_PORT $server_port; 22 | fastcgi_param SERVER_NAME $server_name; 23 | 24 | # PHP only, required if PHP was built with --enable-force-cgi-redirect 25 | fastcgi_param REDIRECT_STATUS 200; 26 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/scgi_params: -------------------------------------------------------------------------------- 1 | 2 | scgi_param REQUEST_METHOD $request_method; 3 | scgi_param REQUEST_URI $request_uri; 4 | scgi_param QUERY_STRING $query_string; 5 | scgi_param CONTENT_TYPE $content_type; 6 | 7 | scgi_param DOCUMENT_URI $document_uri; 8 | scgi_param DOCUMENT_ROOT $document_root; 9 | scgi_param SCGI 1; 10 | scgi_param SERVER_PROTOCOL $server_protocol; 11 | scgi_param REQUEST_SCHEME $scheme; 12 | scgi_param HTTPS $https if_not_empty; 13 | 14 | scgi_param REMOTE_ADDR $remote_addr; 15 | scgi_param REMOTE_PORT $remote_port; 16 | scgi_param SERVER_PORT $server_port; 17 | scgi_param SERVER_NAME $server_name; 18 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/scgi_params.default: -------------------------------------------------------------------------------- 1 | 2 | scgi_param REQUEST_METHOD $request_method; 3 | scgi_param REQUEST_URI $request_uri; 4 | scgi_param QUERY_STRING $query_string; 5 | scgi_param CONTENT_TYPE $content_type; 6 | 7 | scgi_param DOCUMENT_URI $document_uri; 8 | scgi_param DOCUMENT_ROOT $document_root; 9 | scgi_param SCGI 1; 10 | scgi_param SERVER_PROTOCOL $server_protocol; 11 | scgi_param REQUEST_SCHEME $scheme; 12 | scgi_param HTTPS $https if_not_empty; 13 | 14 | scgi_param REMOTE_ADDR $remote_addr; 15 | scgi_param REMOTE_PORT $remote_port; 16 | scgi_param SERVER_PORT $server_port; 17 | scgi_param SERVER_NAME $server_name; 18 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/uwsgi_params: -------------------------------------------------------------------------------- 1 | 2 | uwsgi_param QUERY_STRING $query_string; 3 | uwsgi_param REQUEST_METHOD $request_method; 4 | uwsgi_param CONTENT_TYPE $content_type; 5 | uwsgi_param CONTENT_LENGTH $content_length; 6 | 7 | uwsgi_param REQUEST_URI $request_uri; 8 | uwsgi_param PATH_INFO $document_uri; 9 | uwsgi_param DOCUMENT_ROOT $document_root; 10 | uwsgi_param SERVER_PROTOCOL $server_protocol; 11 | uwsgi_param REQUEST_SCHEME $scheme; 12 | uwsgi_param HTTPS $https if_not_empty; 13 | 14 | uwsgi_param REMOTE_ADDR $remote_addr; 15 | uwsgi_param REMOTE_PORT $remote_port; 16 | uwsgi_param SERVER_PORT $server_port; 17 | uwsgi_param SERVER_NAME $server_name; 18 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/uwsgi_params.default: -------------------------------------------------------------------------------- 1 | 2 | uwsgi_param QUERY_STRING $query_string; 3 | uwsgi_param REQUEST_METHOD $request_method; 4 | uwsgi_param CONTENT_TYPE $content_type; 5 | uwsgi_param CONTENT_LENGTH $content_length; 6 | 7 | uwsgi_param REQUEST_URI $request_uri; 8 | uwsgi_param PATH_INFO $document_uri; 9 | uwsgi_param DOCUMENT_ROOT $document_root; 10 | uwsgi_param SERVER_PROTOCOL $server_protocol; 11 | uwsgi_param REQUEST_SCHEME $scheme; 12 | uwsgi_param HTTPS $https if_not_empty; 13 | 14 | uwsgi_param REMOTE_ADDR $remote_addr; 15 | uwsgi_param REMOTE_PORT $remote_port; 16 | uwsgi_param SERVER_PORT $server_port; 17 | uwsgi_param SERVER_NAME $server_name; 18 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/05-run-docker-compose/dockers/lb/nginx/conf/vhosts/app.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | index index.html index.htm; 4 | add_header 'X-Frame-Options' 'AllowAll'; 5 | location / { 6 | resolver 127.0.0.11 valid=30s; 7 | proxy_redirect off; 8 | proxy_set_header Host $host; 9 | proxy_set_header X-Forwarded-Proto $scheme; 10 | proxy_set_header X-Real-IP $remote_addr; 11 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 12 | set $goto mynginx; 13 | proxy_pass http://$goto; 14 | } 15 | } 16 | upstream mynginx { 17 | server mynginx1:8080; 18 | server mynginx2:8080; 19 | } -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/extra-image-nginx-1.12/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bitnami/minideb-extras:jessie-r20 2 | LABEL maintainer "Bitnami " 3 | 4 | ENV BITNAMI_PKG_CHMOD="-R g+rwX" \ 5 | BITNAMI_PKG_EXTRA_DIRS="/bitnami/nginx/conf" \ 6 | HOME="/" 7 | 8 | # Install required system packages and dependencies 9 | RUN install_packages libc6 libpcre3 libssl1.0.0 zlib1g 10 | RUN bitnami-pkg unpack nginx-1.12.1-2 --checksum 4ed9d05706a333d9d7480ef510025cc550d09d25f321632eb1cf7d037a507deb 11 | RUN ln -sf /opt/bitnami/nginx/html /app 12 | RUN ln -sf /dev/stdout /opt/bitnami/nginx/logs/access.log 13 | RUN ln -sf /dev/stdout /opt/bitnami/nginx/logs/error.log 14 | 15 | COPY rootfs / 16 | 17 | ENV BITNAMI_APP_NAME="nginx" \ 18 | BITNAMI_IMAGE_VERSION="1.12.1-r2" \ 19 | NGINX_DAEMON_GROUP="" \ 20 | NGINX_DAEMON_USER="" \ 21 | NGINX_HTTPS_PORT_NUMBER="8443" \ 22 | NGINX_HTTP_PORT_NUMBER="8080" \ 23 | PATH="/opt/bitnami/nginx/sbin:$PATH" 24 | 25 | EXPOSE 8080 8443 26 | 27 | WORKDIR /app 28 | USER 1001 29 | ENTRYPOINT ["/app-entrypoint.sh"] 30 | CMD ["nginx","-g","daemon off;"] 31 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/extra-image-nginx-1.12/rootfs/app-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . /opt/bitnami/base/functions 4 | . /opt/bitnami/base/helpers 5 | 6 | print_welcome_page 7 | check_for_updates & 8 | 9 | if [[ "$1" == "nami" && "$2" == "start" ]] || [[ "$1" == "nginx" ]]; then 10 | nami_initialize nginx 11 | info "Starting nginx... " 12 | fi 13 | 14 | exec tini -- "$@" 15 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.2-basic-docker/extra-image-nginx-1.12/rootfs/nginx-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpPort": "{{$global.env.NGINX_HTTP_PORT_NUMBER}}", 3 | "httpsPort": "{{$global.env.NGINX_HTTPS_PORT_NUMBER}}", 4 | "systemGroup": "{{$global.env.NGINX_DAEMON_GROUP}}", 5 | "systemUser": "{{$global.env.NGINX_DAEMON_USER}}" 6 | } -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.3-basic-kubernetes/02-run-nginx-with-k8s/00-namespace.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: basic-k8s 5 | labels: 6 | name: basic-k8s 7 | module: Namespace -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.3-basic-kubernetes/02-run-nginx-with-k8s/01-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nginx 5 | namespace: basic-k8s 6 | labels: 7 | name: nginx 8 | spec: 9 | replicas: 1 10 | strategy: 11 | type: Recreate 12 | selector: 13 | matchLabels: 14 | name: nginx 15 | template: 16 | metadata: 17 | labels: 18 | name: nginx 19 | spec: 20 | containers: 21 | - name: nginx 22 | image: 3dsinteractive/nginx:1.12 23 | imagePullPolicy: Always 24 | resources: 25 | requests: 26 | memory: 500Mi 27 | cpu: 200m 28 | limits: 29 | memory: 1Gi 30 | cpu: 500m 31 | livenessProbe: 32 | tcpSocket: 33 | port: 8080 34 | initialDelaySeconds: 5 35 | timeoutSeconds: 1 36 | periodSeconds: 300 37 | readinessProbe: 38 | tcpSocket: 39 | port: 8080 40 | initialDelaySeconds: 5 41 | timeoutSeconds: 1 42 | periodSeconds: 30 43 | failureThreshold: 5 44 | ports: 45 | - containerPort: 8080 46 | name: ngx8080 47 | - containerPort: 8443 48 | name: ngx8443 -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.3-basic-kubernetes/02-run-nginx-with-k8s/02-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: nginx 5 | namespace: basic-k8s 6 | labels: 7 | name: nginx 8 | spec: 9 | selector: 10 | name: nginx 11 | ports: 12 | - name: ngx8080 13 | port: 8080 14 | targetPort: 8080 15 | protocol: TCP 16 | - name: ngx8443 17 | port: 8443 18 | targetPort: 8443 19 | protocol: TCP 20 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.3-basic-kubernetes/02-run-nginx-with-k8s/03-ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: ingress 5 | namespace: basic-k8s 6 | labels: 7 | name: ingress 8 | spec: 9 | rules: 10 | - host: kubernetes.docker.internal 11 | http: 12 | paths: 13 | - path: / 14 | backend: 15 | serviceName: nginx 16 | servicePort: 8080 -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/01-run-redis/00-namespace.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: basic-redis 5 | labels: 6 | name: basic-redis 7 | module: Namespace -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/01-run-redis/01-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: redis 5 | namespace: basic-redis 6 | labels: 7 | name: redis 8 | spec: 9 | replicas: 1 10 | strategy: 11 | type: Recreate 12 | selector: 13 | matchLabels: 14 | name: redis 15 | template: 16 | metadata: 17 | labels: 18 | name: redis 19 | spec: 20 | containers: 21 | - name: redis 22 | image: 3dsinteractive/redis:5.0 23 | imagePullPolicy: Always 24 | ports: 25 | - name: redis6379 26 | containerPort: 6379 27 | env: 28 | - name: ALLOW_EMPTY_PASSWORD 29 | value: "yes" 30 | resources: 31 | requests: 32 | memory: 500Mi 33 | cpu: 200m 34 | limits: 35 | memory: 1Gi 36 | cpu: 500m 37 | livenessProbe: 38 | tcpSocket: 39 | port: 6379 40 | initialDelaySeconds: 30 41 | timeoutSeconds: 1 42 | periodSeconds: 300 43 | readinessProbe: 44 | tcpSocket: 45 | port: 6379 46 | initialDelaySeconds: 30 47 | timeoutSeconds: 1 48 | periodSeconds: 30 49 | failureThreshold: 5 -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/01-run-redis/02-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: redis 5 | namespace: basic-redis 6 | labels: 7 | name: redis 8 | spec: 9 | selector: 10 | name: redis 11 | ports: 12 | - name: redis6379 13 | port: 6379 14 | targetPort: 6379 15 | protocol: TCP 16 | clusterIP: None -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/01-run-redis/03-client-util.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: client-util 5 | namespace: basic-redis 6 | labels: 7 | name: client-util 8 | spec: 9 | containers: 10 | - name: client-util 11 | image: opcellent/util:2.0 12 | stdin: true 13 | tty: true 14 | imagePullPolicy: Always 15 | resources: 16 | requests: 17 | memory: 500Mi 18 | cpu: 200m 19 | limits: 20 | memory: 1Gi 21 | cpu: 500m 22 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/01-run-redis/README: -------------------------------------------------------------------------------- 1 | Run Redis 2 | === 3 | 4 | 1. Enter workshop dir 5 | $ cd chapter01-basic-knowledges/1.4-basic-redis/01-run-redis 6 | 7 | 2. Create k8s namespace 8 | $ kubectl apply -f 00-namespace.yml 9 | namespace/basic-redis created 10 | 11 | 3. Check if namespace has created 12 | $ kubectl get ns 13 | NAME STATUS AGE 14 | basic-redis Active 20s 15 | default Active 7h59m 16 | ingress-nginx Active 46m 17 | kube-node-lease Active 7h59m 18 | kube-public Active 7h59m 19 | kube-system Active 7h59m 20 | 21 | 4. Create redis deployment 22 | $ kubectl apply -f 01-deployment.yml 23 | deployment.apps/redis created 24 | 25 | 5. Check redis deployment has created 26 | $ kubectl get po -n basic-redis 27 | NAME READY STATUS RESTARTS AGE 28 | redis-577d58dd6c-g7brv 0/1 Running 0 26s 29 | 30 | ** Wait until the STATUS is Running 31 | 32 | 6. Create redis service 33 | $ kubectl apply -f 02-service.yml 34 | service/redis created 35 | 36 | 7. Check redis service has created 37 | $ kubectl get svc -n basic-redis 38 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 39 | redis ClusterIP None 6379/TCP 19s 40 | 41 | 8. Create client-util pod 42 | $ kubectl apply -f 03-client-util.yml 43 | pod/client-util created 44 | 45 | 9. Check if client-util pod has created 46 | $ kubectl get po -n basic-redis 47 | NAME READY STATUS RESTARTS AGE 48 | client-util 1/1 Running 0 96s 49 | redis-577d58dd6c-g7brv 1/1 Running 0 3m54s 50 | 51 | ** Wait until the STATUS is Running 52 | 53 | 10. Exec into client-util pod 54 | $ kubectl exec -it client-util -n basic-redis -- bash 55 | root@client-util:/# 56 | 57 | 11. Run redis-cli to connect to redis 58 | # redis-cli -h redis 59 | redis:6379> 60 | 61 | 12. Exit from redis-cli 62 | # exit 63 | root@client-util:/# 64 | 65 | 13. Exit from client-util 66 | # exit 67 | 68 | 14. Do not cleanup workshop, we will use it in next workshop -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/02-use-redis-cli/README: -------------------------------------------------------------------------------- 1 | Use Redis cli 2 | === 3 | 4 | 1. Exec into client-util pod 5 | $ kubectl exec -it client-util -n basic-redis -- bash 6 | 7 | 2. Start redis-cli 8 | $ redis-cli -h redis 9 | redis:6379> 10 | 11 | 3. Use SET command to set mykey = myvalue 12 | $ SET "mykey" "myvalue" 13 | OK 14 | 15 | 4. Use GET to get value from key 16 | $ GET "mykey" 17 | "myvalue" 18 | 19 | 5. Use EXPIRE command to expire mykey in 10 seconds 20 | $ EXPIRE "mykey" 10 21 | (integer) 1 22 | 23 | ** Wait 10 seconds 24 | 25 | 6. Test get mykey when it is expired 26 | $ GET "mykey" 27 | (nil) 28 | 29 | 7. SET mykey2 30 | $ SET "mykey2" "myvalue2" 31 | OK 32 | 33 | 8. GET mykey2 34 | $ GET "mykey2" 35 | "myvalue2" 36 | 37 | 9. Use DEL command to delete mykey2 38 | $ DEL "mykey2" 39 | (integer) 1 40 | 41 | 10. GET mykey2 to see how it is deleted 42 | $ GET "mykey2" 43 | (nil) 44 | 45 | 11. SET mykey1 and mykey2 46 | $ SET "mykey1" "value1" 47 | $ SET "mykey2" "value2" 48 | 49 | 12. Use KEYS to list all keys using wildcard 50 | $ KEYS "mykey*" 51 | 1) "mykey2" 52 | 2) "mykey1" 53 | 54 | 13. Exit from redis 55 | $ exit 56 | 57 | 14. Exit from client-util 58 | $ exit 59 | 60 | 15. Cleanup workshop 61 | $ kubectl delete ns basic-redis -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/extra-redis-image/redis-5.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bitnami/minideb-extras-base:stretch-r266 2 | LABEL maintainer "Bitnami " 3 | 4 | ENV BITNAMI_PKG_CHMOD="-R g+rwX" \ 5 | HOME="/" \ 6 | OS_ARCH="amd64" \ 7 | OS_FLAVOUR="debian-9" \ 8 | OS_NAME="linux" 9 | 10 | # Install required system packages and dependencies 11 | RUN install_packages libc6 12 | RUN . ./libcomponent.sh && component_unpack "redis" "5.0.5-1" --checksum f43c5625691360ae2c5a43aebb772b9ae05a3268efabe5c0474e0804505f0a9a 13 | 14 | COPY rootfs / 15 | RUN /postunpack.sh 16 | ENV ALLOW_EMPTY_PASSWORD="no" \ 17 | BITNAMI_APP_NAME="redis" \ 18 | BITNAMI_IMAGE_VERSION="5.0.5-debian-9-r30" \ 19 | NAMI_PREFIX="/.nami" \ 20 | PATH="/opt/bitnami/redis/bin:$PATH" \ 21 | REDIS_DISABLE_COMMANDS="" \ 22 | REDIS_MASTER_HOST="" \ 23 | REDIS_MASTER_PASSWORD="" \ 24 | REDIS_MASTER_PASSWORD_FILE="" \ 25 | REDIS_MASTER_PORT_NUMBER="6379" \ 26 | REDIS_PASSWORD="" \ 27 | REDIS_PASSWORD_FILE="" \ 28 | REDIS_REPLICATION_MODE="" 29 | 30 | EXPOSE 6379 31 | 32 | USER 1001 33 | ENTRYPOINT [ "/entrypoint.sh" ] 34 | CMD [ "/run.sh" ] 35 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/extra-redis-image/redis-5.0/rootfs/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | #set -o xtrace 7 | # shellcheck disable=SC1091 8 | 9 | # Load libraries 10 | . /libbitnami.sh 11 | . /libredis.sh 12 | 13 | # Load Redis environment variables 14 | eval "$(redis_env)" 15 | 16 | print_welcome_page 17 | 18 | if [[ "$*" = *"/run.sh"* ]]; then 19 | info "** Starting Redis setup **" 20 | /setup.sh 21 | info "** Redis setup finished! **" 22 | fi 23 | 24 | echo "" 25 | exec "$@" 26 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/extra-redis-image/redis-5.0/rootfs/postunpack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=SC1091 4 | 5 | # Load libraries 6 | . /libredis.sh 7 | . /libfs.sh 8 | 9 | # Load Redis environment variables 10 | eval "$(redis_env)" 11 | 12 | for dir in "$REDIS_VOLUME" "${REDIS_VOLUME}/data" ; do 13 | ensure_dir_exists "$dir" 14 | done 15 | chmod -R g+rwX /bitnami "$REDIS_VOLUME" "$REDIS_BASEDIR" 16 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/extra-redis-image/redis-5.0/rootfs/redis-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowEmptyPassword": "{{$global.env.ALLOW_EMPTY_PASSWORD}}", 3 | "disableCommands": "{{$global.env.REDIS_DISABLE_COMMANDS}}", 4 | "masterHost": "{{$global.env.REDIS_MASTER_HOST}}", 5 | "masterPassword": "{{$global.env.REDIS_MASTER_PASSWORD}}", 6 | "masterPasswordFileLocation": "{{$global.env.REDIS_MASTER_PASSWORD_FILE}}", 7 | "masterPort": "{{$global.env.REDIS_MASTER_PORT_NUMBER}}", 8 | "password": "{{$global.env.REDIS_PASSWORD}}", 9 | "passwordFileLocation": "{{$global.env.REDIS_PASSWORD_FILE}}", 10 | "replicationMode": "{{$global.env.REDIS_REPLICATION_MODE}}" 11 | } -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/extra-redis-image/redis-5.0/rootfs/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | # set -o xtrace 7 | # shellcheck disable=SC1091 8 | 9 | # Load libraries 10 | . /libos.sh 11 | . /libredis.sh 12 | 13 | # Load Redis environment variables 14 | eval "$(redis_env)" 15 | 16 | # Constants 17 | REDIS_EXTRA_FLAGS=${REDIS_EXTRA_FLAGS:-} 18 | EXEC=$(command -v redis-server) 19 | 20 | args=("$REDIS_BASEDIR/etc/redis.conf" "--daemonize" "no" "$@") 21 | # configure extra command line flags 22 | if [[ -n "$REDIS_EXTRA_FLAGS" ]]; then 23 | warn "REDIS_EXTRA_FLAGS is deprecated. Please specify any extra-flag using '/run.sh $REDIS_EXTRA_FLAGS' as command instead" 24 | read -r -a envExtraFlags <<< "$REDIS_EXTRA_FLAGS" 25 | args+=("${envExtraFlags[@]}") 26 | fi 27 | 28 | info "** Starting Redis **" 29 | if am_i_root; then 30 | exec gosu "$REDIS_DAEMON_USER" "$EXEC" "${args[@]}" 31 | else 32 | exec "$EXEC" "${args[@]}" 33 | fi 34 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.4-basic-redis/extra-redis-image/redis-5.0/rootfs/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | #set -o xtrace 7 | # shellcheck disable=SC1091 8 | 9 | # Load libraries 10 | . /libos.sh 11 | . /libfs.sh 12 | . /libredis.sh 13 | 14 | # Load Redis environment variables 15 | eval "$(redis_env)" 16 | 17 | # Ensure Redis environment variables settings are valid 18 | redis_validate 19 | # Ensure Redis is stopped when this script ends 20 | trap "redis_stop" EXIT 21 | am_i_root && ensure_user_exists "$REDIS_DAEMON_USER" "$REDIS_DAEMON_GROUP" 22 | # Ensure Redis is initialized 23 | redis_initialize 24 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/01-run-kafka/00-namespace.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: basic-kafka 5 | labels: 6 | name: basic-kafka 7 | module: Namespace -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/01-run-kafka/02-service-zk.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: zk1 5 | namespace: basic-kafka 6 | labels: 7 | name: zk1 8 | spec: 9 | selector: 10 | name: zk1 11 | ports: 12 | - name: zk2181 13 | port: 2181 14 | protocol: TCP 15 | - name: zk2888 16 | port: 2888 17 | protocol: TCP 18 | - name: zk3888 19 | port: 3888 20 | protocol: TCP 21 | clusterIP: None 22 | --- 23 | apiVersion: v1 24 | kind: Service 25 | metadata: 26 | name: zk2 27 | namespace: basic-kafka 28 | labels: 29 | name: zk2 30 | spec: 31 | selector: 32 | name: zk2 33 | ports: 34 | - name: zk2181 35 | port: 2181 36 | protocol: TCP 37 | - name: zk2888 38 | port: 2888 39 | protocol: TCP 40 | - name: zk3888 41 | port: 3888 42 | protocol: TCP 43 | clusterIP: None 44 | --- 45 | apiVersion: v1 46 | kind: Service 47 | metadata: 48 | name: zk3 49 | namespace: basic-kafka 50 | labels: 51 | name: zk3 52 | spec: 53 | selector: 54 | name: zk3 55 | ports: 56 | - name: zk2181 57 | port: 2181 58 | protocol: TCP 59 | - name: zk2888 60 | port: 2888 61 | protocol: TCP 62 | - name: zk3888 63 | port: 3888 64 | protocol: TCP 65 | clusterIP: None -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/01-run-kafka/04-service-kfk.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: kfk1 5 | namespace: basic-kafka 6 | labels: 7 | name: kfk1 8 | spec: 9 | selector: 10 | name: kfk1 11 | ports: 12 | - name: kfk9092 13 | port: 9092 14 | protocol: TCP 15 | clusterIP: None 16 | --- 17 | apiVersion: v1 18 | kind: Service 19 | metadata: 20 | name: kfk2 21 | namespace: basic-kafka 22 | labels: 23 | name: kfk2 24 | spec: 25 | selector: 26 | name: kfk2 27 | ports: 28 | - name: kfk9092 29 | port: 9092 30 | protocol: TCP 31 | clusterIP: None 32 | --- 33 | apiVersion: v1 34 | kind: Service 35 | metadata: 36 | name: kfk3 37 | namespace: basic-kafka 38 | labels: 39 | name: kfk3 40 | spec: 41 | selector: 42 | name: kfk3 43 | ports: 44 | - name: kfk9092 45 | port: 9092 46 | protocol: TCP 47 | clusterIP: None -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/01-run-kafka/05-client-util.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: client-util 5 | namespace: basic-kafka 6 | labels: 7 | name: client-util 8 | spec: 9 | containers: 10 | - name: client-util 11 | image: opcellent/util:2.0 12 | stdin: true 13 | tty: true 14 | imagePullPolicy: Always 15 | resources: 16 | requests: 17 | memory: 500Mi 18 | cpu: 200m 19 | limits: 20 | memory: 1Gi 21 | cpu: 500m 22 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/02-play-consumer-producer/README: -------------------------------------------------------------------------------- 1 | Play Consumer-Producer 2 | === 3 | 4 | Kafkacat URL 5 | https://github.com/edenhill/kafkacat 6 | 7 | 1. Start all k8s object if not created 8 | $ kubectl apply -f . 9 | 10 | 2. Check if kafka and zookeeper pods is running 11 | $ kubectl get po -n basic-kafka 12 | NAME READY STATUS RESTARTS AGE 13 | client-util 1/1 Running 0 5m28s 14 | kfk1-86886b6b84-75p7p 1/1 Running 2 5m29s 15 | kfk2-5b69dfcdb4-wk9lt 1/1 Running 2 5m29s 16 | kfk3-6d4c8874c6-47dxp 1/1 Running 2 5m29s 17 | zk1-76cc547698-8865g 1/1 Running 0 5m29s 18 | zk2-7bb59d6788-5dfxx 1/1 Running 0 5m29s 19 | zk3-566db54d6b-xqjzh 1/1 Running 0 5m29s 20 | 21 | 3. Exec into client-util pod 22 | $ kubectl exec -it client-util -n basic-kafka -- bash 23 | root@client-util:/# 24 | 25 | 4. Start Producer using kafkacat, and send some messages 26 | $ kafkacat -P -b "kfk1,kfk2,kfk3" -t "mytopic" 27 | 28 | $ message1 29 | $ message2 30 | $ message3 31 | 32 | 5. Open another tab in terminal, and exec into client-util 33 | $ kubectl exec -it client-util -n basic-kafka -- bash 34 | root@client-util:/# 35 | 36 | 6. Start Consumer in the open terminal 37 | $ kafkacat -C -b "kfk1,kfk2,kfk3" -t "mytopic" 38 | 39 | 7. Exit Consumer by (Ctrl + c) 40 | 41 | 8. Exit client-util 42 | $ exit 43 | 44 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/03-play-consumer-group/README: -------------------------------------------------------------------------------- 1 | Play Consumer Group 2 | === 3 | 4 | Kafkacat URL 5 | https://github.com/edenhill/kafkacat 6 | 7 | 1. Start all k8s objects 8 | $ kubectl apply -f . 9 | 10 | 2. Check if kafka and zookeeper deployment is Running 11 | $ kubectl get po -n basic-kafka 12 | NAME READY STATUS RESTARTS AGE 13 | client-util 1/1 Running 0 5m28s 14 | kfk1-86886b6b84-75p7p 1/1 Running 2 5m29s 15 | kfk2-5b69dfcdb4-wk9lt 1/1 Running 2 5m29s 16 | kfk3-6d4c8874c6-47dxp 1/1 Running 2 5m29s 17 | zk1-76cc547698-8865g 1/1 Running 0 5m29s 18 | zk2-7bb59d6788-5dfxx 1/1 Running 0 5m29s 19 | zk3-566db54d6b-xqjzh 1/1 Running 0 5m29s 20 | 21 | 3. Exec into client-util 22 | $ kubectl exec -it client-util -n basic-kafka -- bash 23 | root@client-util:/# 24 | 25 | 4. Start Producer and send some messages 26 | $ kafkacat -P -b "kfk1,kfk2,kfk3" -t "mytopic1" 27 | 28 | $ message1 29 | $ message2 30 | $ message3 31 | 32 | 5. Open another tab in terminal, then exec into client-util shell 33 | $ kubectl exec -it client-util -n basic-kafka -- bash 34 | root@client-util:/# 35 | 36 | 6. Start Consumer 1 in new terminal 37 | $ kafkacat -C -b "kfk1,kfk2,kfk3" -G mygroup mytopic1 38 | 39 | 7. Open another tab in terminal, then exec into client-util shell 40 | $ kubectl exec -it client-util -n basic-kafka -- bash 41 | root@client-util:/# 42 | 43 | 8. Start Consumer 2 in new terminal 44 | $ kafkacat -C -b "kfk1,kfk2,kfk3" -G mygroup mytopic1 45 | 46 | 9. Send some more messages in Producer 47 | $ message4 48 | $ message5 49 | $ message6 50 | $ message7 51 | $ message8 52 | 53 | 10. Check each Consumers will receive new messages 54 | 55 | 12. Exit from Consumer using (Ctrl + c) 56 | 57 | 13. When Consumer exit, see the rebalance process happen 58 | 59 | 14. Exit from all consumer using (Ctrl + c) 60 | 61 | 15. Exit from all client-util 62 | $ exit 63 | 64 | 16. Cleanup workshop 65 | $ kubectl delete ns basic-kafka 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/extra-docker-images/kfk-2.0/rootfs/app-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . /opt/bitnami/base/functions 4 | . /opt/bitnami/base/helpers 5 | 6 | print_welcome_page 7 | 8 | if [[ "$1" == "nami" && "$2" == "start" ]] || [[ "$1" == "/run.sh" ]]; then 9 | . /init.sh 10 | nami_initialize kafka 11 | info "Starting kafka... " 12 | fi 13 | 14 | exec tini -- "$@" 15 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/extra-docker-images/kfk-2.0/rootfs/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## 4 | ## @brief Helper function to show an error when KAFKA_LISTENERS does not configure a secure listener 5 | ## param $1 Input name 6 | ## 7 | plaintext_listener_error() { 8 | error "The $1 environment variable does not set a secure listener. Set the environment variable ALLOW_PLAINTEXT_LISTENER=yes to allow the container to be started with a plaintext listener. This is recommended only for development." 9 | exit 1 10 | } 11 | 12 | ## 13 | ## @brief Helper function to show a warning when the ALLOW_PLAINTEXT_LISTENER flag is enabled 14 | ## 15 | plaintext_listener_enabled_warn() { 16 | warn "You set the environment variable ALLOW_PLAINTEXT_LISTENER=${ALLOW_PLAINTEXT_LISTENER}. For safety reasons, do not use this flag in a production environment." 17 | } 18 | 19 | 20 | # Validate passwords 21 | if [[ "$ALLOW_PLAINTEXT_LISTENER" =~ ^(yes|Yes|YES)$ ]]; then 22 | plaintext_listener_enabled_warn 23 | elif [[ ! "$KAFKA_LISTENERS" =~ SASL_SSL ]]; then 24 | plaintext_listener_error KAFKA_LISTENERS 25 | fi 26 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/extra-docker-images/kfk-2.0/rootfs/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | . /opt/bitnami/base/functions 3 | . /opt/bitnami/base/helpers 4 | 5 | 6 | USER=kafka 7 | KAFKA_HOME="/opt/bitnami/kafka" 8 | START_COMMAND="${KAFKA_HOME}/bin/kafka-server-start.sh ${KAFKA_HOME}/config/server.properties --override connections.max.idle.ms=-1 --override group.initial.rebalance.delay.ms=3000" 9 | 10 | if [[ -z "$KAFKA_BROKER_ID" ]]; then 11 | if [[ -n "$BROKER_ID_COMMAND" ]]; then 12 | KAFKA_BROKER_ID="$(eval "$BROKER_ID_COMMAND")" 13 | export KAFKA_BROKER_ID 14 | else 15 | # By default auto allocate broker ID 16 | export KAFKA_BROKER_ID=-1 17 | fi 18 | fi 19 | 20 | if [[ "$KAFKA_LISTENERS" =~ SASL ]]; then 21 | export KAFKA_OPTS="-Djava.security.auth.login.config=${KAFKA_HOME}/conf/kafka_jaas.conf" 22 | fi 23 | 24 | # If container is started as `root` user 25 | if [[ $EUID -eq 0 ]]; then 26 | exec gosu ${USER} bash -c "${START_COMMAND[@]}" 27 | else 28 | exec bash -c "${START_COMMAND[@]}" 29 | fi 30 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/extra-docker-images/zk-3.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bitnami/minideb-extras:stretch-r163 2 | LABEL maintainer "Bitnami " 3 | 4 | ENV BITNAMI_PKG_CHMOD="-R g+rwX" \ 5 | HOME="/" 6 | 7 | # Install required system packages and dependencies 8 | RUN install_packages libblkid1 libbsd0 libc6 libffi6 libgcc1 libglib2.0-0 libmount1 libpcre3 libselinux1 libstdc++6 libuuid1 libx11-6 libxau6 libxcb1 libxdmcp6 libxext6 zlib1g 9 | RUN bitnami-pkg install java-1.8.181-1 --checksum 66bba4b4a2647f981339d306da796905c222057c4277a5ef045e079981a404f4 10 | RUN bitnami-pkg unpack zookeeper-3.4.12-14 --checksum 06a1205a7955d26bdbd8f72cfabc39d10724c5a4ec9da39df20de4012bf5abb3 11 | 12 | COPY rootfs / 13 | ENV ALLOW_ANONYMOUS_LOGIN="no" \ 14 | BITNAMI_APP_NAME="zookeeper" \ 15 | BITNAMI_IMAGE_VERSION="3.4.12-debian-9-r75" \ 16 | JVMFLAGS="" \ 17 | PATH="/opt/bitnami/java/bin:/opt/bitnami/zookeeper/bin:$PATH" \ 18 | ZOO_CLIENT_PASSWORD="" \ 19 | ZOO_CLIENT_USER="" \ 20 | ZOO_ENABLE_AUTH="no" \ 21 | ZOO_HEAP_SIZE="1024" \ 22 | ZOO_INIT_LIMIT="10" \ 23 | ZOO_MAX_CLIENT_CNXNS="60" \ 24 | ZOO_PORT_NUMBER="2181" \ 25 | ZOO_SERVERS="" \ 26 | ZOO_SERVER_ID="1" \ 27 | ZOO_SERVER_PASSWORDS="" \ 28 | ZOO_SERVER_USERS="" \ 29 | ZOO_SYNC_LIMIT="5" \ 30 | ZOO_TICK_TIME="2000" 31 | 32 | EXPOSE 2181 2888 3888 33 | 34 | USER 1001 35 | ENTRYPOINT [ "/app-entrypoint.sh" ] 36 | CMD [ "/run.sh" ] 37 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/extra-docker-images/zk-3.0/rootfs/app-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . /opt/bitnami/base/functions 4 | . /opt/bitnami/base/helpers 5 | 6 | print_welcome_page 7 | 8 | if [[ "$1" == "nami" && "$2" == "start" ]] || [[ "$1" == "/run.sh" ]]; then 9 | . /init.sh 10 | nami_initialize zookeeper 11 | info "Starting zookeeper... " 12 | fi 13 | 14 | exec tini -- "$@" 15 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/extra-docker-images/zk-3.0/rootfs/init.sh: -------------------------------------------------------------------------------- 1 | ## 2 | ## @brief Helper function to show an error when ZOO_ENABLE_AUTH is set to no 3 | ## param $1 Input name 4 | ## 5 | anonymous_login_error() { 6 | error "The $1 environment variable does not configure authentication. Set the environment variable ALLOW_ANONYMOUS_LOGIN=yes to allow unauthenticated users to connect to ZooKeeper. This is recommended only for development." 7 | exit 1 8 | } 9 | 10 | ## 11 | ## @brief Helper function to show a warning when the ALLOW_ANONYMOUS_LOGIN flag is enabled 12 | ## 13 | anonymous_login_enabled_warn() { 14 | warn "You set the environment variable ALLOW_ANONYMOUS_LOGIN=${ALLOW_ANONYMOUS_LOGIN}. For safety reasons, do not use this flag in a production environment." 15 | } 16 | 17 | 18 | # Validate passwords 19 | if [[ "$ALLOW_ANONYMOUS_LOGIN" =~ ^(yes|Yes|YES)$ ]]; then 20 | anonymous_login_enabled_warn 21 | elif [[ ! "$ZOO_ENABLE_AUTH" =~ ^(yes|Yes|YES)$ ]]; then 22 | anonymous_login_error ZOO_ENABLE_AUTH 23 | fi 24 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/extra-docker-images/zk-3.0/rootfs/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | . /opt/bitnami/base/functions 3 | . /opt/bitnami/base/helpers 4 | 5 | 6 | export ZOO_LOG_DIR=/opt/bitnami/zookeeper/logs 7 | export ZOOPIDFILE=/opt/bitnami/zookeeper/tmp/zookeeper.pid 8 | 9 | USER=zookeeper 10 | START_COMMAND="/opt/bitnami/zookeeper/bin/zkServer.sh start && tail -f ${ZOO_LOG_DIR}/zookeeper.out" 11 | 12 | # If container is started as `root` user 13 | if [ $EUID -eq 0 ]; then 14 | exec gosu ${USER} bash -c "${START_COMMAND}" 15 | else 16 | exec bash -c "${START_COMMAND}" 17 | fi 18 | -------------------------------------------------------------------------------- /chapter01-basic-knowledges/1.5-basic-kafka/extra-docker-images/zk-3.0/rootfs/zookeeper-inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowAnonymousLogin": "{{$global.env.ALLOW_ANONYMOUS_LOGIN}}", 3 | "zookeeperClientPassword": "{{$global.env.ZOO_CLIENT_PASSWORD}}", 4 | "zookeeperClientUser": "{{$global.env.ZOO_CLIENT_USER}}", 5 | "zookeeperEnableAuth": "{{$global.env.ZOO_ENABLE_AUTH}}", 6 | "zookeeperHeapSize": "{{$global.env.ZOO_HEAP_SIZE}}", 7 | "zookeeperId": "{{$global.env.ZOO_SERVER_ID}}", 8 | "zookeeperInitLimit": "{{$global.env.ZOO_INIT_LIMIT}}", 9 | "zookeeperJvmFlags": "{{$global.env.JVMFLAGS}}", 10 | "zookeeperMaxClientCnxns": "{{$global.env.ZOO_MAX_CLIENT_CNXNS}}", 11 | "zookeeperPort": "{{$global.env.ZOO_PORT_NUMBER}}", 12 | "zookeeperServerPasswords": "{{$global.env.ZOO_SERVER_PASSWORDS}}", 13 | "zookeeperServerUsers": "{{$global.env.ZOO_SERVER_USERS}}", 14 | "zookeeperServers": "{{$global.env.ZOO_SERVERS}}", 15 | "zookeeperSyncLimit": "{{$global.env.ZOO_SYNC_LIMIT}}", 16 | "zookeeperTickTime": "{{$global.env.ZOO_TICK_TIME}}" 17 | } -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/01-http-service/README: -------------------------------------------------------------------------------- 1 | HTTP service 2 | 3 | 1. Open chapter02-microservices/2.2-microservices-types/01-http-service 4 | 5 | 2. Run command 6 | $ go mod init automationworkshop/main 7 | go: creating new go.mod: module automationworkshop/main 8 | 9 | 3. Run command 10 | $ go build . 11 | $ ./main 12 | 13 | 4. Run command 14 | $ curl -X POST "localhost:8080/citizen" 15 | {"status":"success"} 16 | 17 | 5. Run command 18 | $ curl -X PUT "localhost:8080/citizen/123" 19 | {"id":"123"} 20 | 21 | 6. Run command 22 | $ curl -X GET "localhost:8080/citizen/123?page=2" 23 | {"id":"123","page":"2"} 24 | 25 | 7. Run command 26 | $ curl -X DELETE "localhost:8080/citizen/123" 27 | {"status":"success"} 28 | 29 | 8. Explain the service in source code -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/01-http-service/context.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // IContext is the context for service 4 | type IContext interface { 5 | Log(message string) 6 | Param(name string) string 7 | QueryParam(name string) string 8 | ReadInput() string 9 | Response(responseCode int, responseData interface{}) 10 | } 11 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/01-http-service/context_http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | 7 | "github.com/labstack/echo" 8 | ) 9 | 10 | // HTTPContext implement IContext it is context for HTTP 11 | type HTTPContext struct { 12 | ms *Microservice 13 | c echo.Context 14 | } 15 | 16 | // NewHTTPContext is the constructor function for HTTPContext 17 | func NewHTTPContext(ms *Microservice, c echo.Context) *HTTPContext { 18 | return &HTTPContext{ 19 | ms: ms, 20 | c: c, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *HTTPContext) Log(message string) { 26 | fmt.Println("HTTP: ", message) 27 | } 28 | 29 | // Param return parameter by name 30 | func (ctx *HTTPContext) Param(name string) string { 31 | return ctx.c.Param(name) 32 | } 33 | 34 | // QueryParam return query param 35 | func (ctx *HTTPContext) QueryParam(name string) string { 36 | return ctx.c.QueryParam(name) 37 | } 38 | 39 | // ReadInput read the request body and return it as string 40 | func (ctx *HTTPContext) ReadInput() string { 41 | body, err := ioutil.ReadAll(ctx.c.Request().Body) 42 | if err != nil { 43 | return "" 44 | } 45 | return string(body) 46 | } 47 | 48 | // Response return response to client 49 | func (ctx *HTTPContext) Response(responseCode int, responseData interface{}) { 50 | ctx.c.JSON(responseCode, responseData) 51 | } 52 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/01-http-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func main() { 8 | 9 | ms := NewMicroservice() 10 | 11 | ms.POST("/citizen", func(ctx IContext) error { 12 | ctx.Log("POST: /citizen") 13 | status := map[string]interface{}{ 14 | "status": "success", 15 | } 16 | ctx.Response(http.StatusOK, status) 17 | return nil 18 | }) 19 | 20 | ms.GET("/citizen/:id", func(ctx IContext) error { 21 | id := ctx.Param("id") 22 | page := ctx.QueryParam("page") 23 | ctx.Log("GET: /citizen/" + id) 24 | citizen := map[string]interface{}{ 25 | "id": id, 26 | "page": page, 27 | } 28 | ctx.Response(http.StatusOK, citizen) 29 | return nil 30 | }) 31 | 32 | ms.PUT("/citizen/:id", func(ctx IContext) error { 33 | id := ctx.Param("id") 34 | ctx.Log("PUT: /citizen/" + id) 35 | citizen := map[string]interface{}{ 36 | "id": id, 37 | } 38 | ctx.Response(http.StatusOK, citizen) 39 | return nil 40 | }) 41 | 42 | ms.DELETE("/citizen/:id", func(ctx IContext) error { 43 | id := ctx.Param("id") 44 | ctx.Log("DELETE: /citizen/" + id) 45 | status := map[string]interface{}{ 46 | "status": "success", 47 | } 48 | ctx.Response(http.StatusOK, status) 49 | return nil 50 | }) 51 | 52 | defer ms.Cleanup() 53 | ms.Start() 54 | } 55 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/02-consumer-service/README: -------------------------------------------------------------------------------- 1 | Consumer Service 2 | 3 | 0. Look at slides Consumer service 4 | 5 | 1. Open vscode at chapter02-microservices/2.2-microservices-types/02-consumer-service 6 | 7 | 2. Start kafka and zookeeper locally via docker 8 | $ docker-compose up -d 9 | * Wait until container is ready 10 | 11 | $ docker ps 12 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 13 | eecc315cc549 3dsinteractive/kafka:2.0 "/app-entrypoint.sh …" 2 minutes ago Up 2 minutes 9092/tcp, 0.0.0.0:9094->9094/tcp 02-consumer-service_kafka_1 14 | 67b5f2efaf0a 3dsinteractive/zookeeper:3.0 "/app-entrypoint.sh …" 2 minutes ago Up 2 minutes 2888/tcp, 0.0.0.0:2181->2181/tcp, 3888/tcp 02-consumer-service_zookeeper_1 15 | 16 | 3. Run command 17 | $ go mod init automationworkshop/main 18 | go: creating new go.mod: module automationworkshop/main 19 | 20 | 4. Run command 21 | $ go build . 22 | $ ./main 23 | 24 | Consumer: {"message_id":0} 25 | Consumer: {"message_id":1} 26 | Consumer: {"message_id":2} 27 | Consumer: {"message_id":3} 28 | Consumer: {"message_id":4} 29 | Consumer: {"message_id":5} 30 | Consumer: {"message_id":6} 31 | Consumer: {"message_id":7} 32 | Consumer: {"message_id":8} 33 | Consumer: {"message_id":9} 34 | 35 | 5. Run command for cleanup 36 | $ docker-compose down 37 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/02-consumer-service/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | // IContext is the context for service 5 | type IContext interface { 6 | Log(message string) 7 | Param(name string) string 8 | Response(responseCode int, responseData interface{}) 9 | ReadInput() string 10 | } 11 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/02-consumer-service/context_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import "fmt" 5 | 6 | // ConsumerContext implement IContext it is context for Consumer 7 | type ConsumerContext struct { 8 | ms *Microservice 9 | message string 10 | } 11 | 12 | // NewConsumerContext is the constructor function for ConsumerContext 13 | func NewConsumerContext(ms *Microservice, message string) *ConsumerContext { 14 | return &ConsumerContext{ 15 | ms: ms, 16 | message: message, 17 | } 18 | } 19 | 20 | // Log will log a message 21 | func (ctx *ConsumerContext) Log(message string) { 22 | fmt.Println("Consumer: ", message) 23 | } 24 | 25 | // Param return parameter by name (empty in case of Consumer) 26 | func (ctx *ConsumerContext) Param(name string) string { 27 | return "" 28 | } 29 | 30 | // ReadInput return message 31 | func (ctx *ConsumerContext) ReadInput() string { 32 | return ctx.message 33 | } 34 | 35 | // Response return response to client 36 | func (ctx *ConsumerContext) Response(responseCode int, responseData interface{}) { 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/02-consumer-service/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | zookeeper: 4 | image: 3dsinteractive/zookeeper:3.0 5 | ports: 6 | - 2181:2181 7 | environment: 8 | - ALLOW_ANONYMOUS_LOGIN=yes 9 | kafka: 10 | image: 3dsinteractive/kafka:2.0-custom 11 | ports: 12 | - 9094:9094 13 | environment: 14 | - ALLOW_PLAINTEXT_LISTENER=yes 15 | - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 16 | - KAFKA_ADVERTISED_LISTENERS=INSIDE://:9092,OUTSIDE://localhost:9094 17 | - KAFKA_LISTENERS=INSIDE://:9092,OUTSIDE://:9094 18 | - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT 19 | - KAFKA_INTER_BROKER_LISTENER_NAME=INSIDE 20 | - KAFKA_DELETE_TOPIC_ENABLE=true 21 | - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true 22 | - KAFKA_NUM_NETWORK_THREADS=8 23 | - KAFKA_NUM_IO_THREADS=16 24 | depends_on: 25 | - zookeeper -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/02-consumer-service/main.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ms := NewMicroservice() 10 | 11 | servers := "localhost:9094" 12 | topic := "when-citizen-has-registered-" + randString() 13 | groupID := "validation-consumer" 14 | timeout := time.Duration(-1) 15 | 16 | ms.Consume(servers, topic, groupID, timeout, func(ctx IContext) error { 17 | msg := ctx.ReadInput() 18 | ctx.Log(msg) 19 | return nil 20 | }) 21 | 22 | prod := NewProducer(servers, ms) 23 | go func() { 24 | for i := 0; i < 10; i++ { 25 | prod.SendMessage(topic, "", map[string]interface{}{"message_id": i}) 26 | time.Sleep(time.Second) 27 | } 28 | 29 | // Exit program 30 | ms.Stop() 31 | }() 32 | 33 | defer ms.Cleanup() 34 | ms.Start() 35 | } 36 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/02-consumer-service/util.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | ) 8 | 9 | func randString() string { 10 | i := rand.Int() 11 | return fmt.Sprintf("%d", i) 12 | } 13 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/03-batch-consumer-service/README: -------------------------------------------------------------------------------- 1 | Batch Consumer 2 | 3 | 0. Look at slide Batch Consumer Service 4 | 5 | 1. open vscode at chapter02-microservices/2.2-microservices-types/03-batch-consumer-service 6 | 7 | 2. Start kafka and zookeeper locally via docker 8 | $ docker-compose up -d 9 | * Wait until container is ready 10 | 11 | $ docker ps 12 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 13 | eecc315cc549 3dsinteractive/kafka:2.0 "/app-entrypoint.sh …" 2 minutes ago Up 2 minutes 9092/tcp, 0.0.0.0:9094->9094/tcp 02-consumer-service_kafka_1 14 | 67b5f2efaf0a 3dsinteractive/zookeeper:3.0 "/app-entrypoint.sh …" 2 minutes ago Up 2 minutes 2888/tcp, 0.0.0.0:2181->2181/tcp, 3888/tcp 02-consumer-service_zookeeper_1 15 | 16 | 3. Run command 17 | $ go mod init automationworkshop/main 18 | go: creating new go.mod: module automationworkshop/main 19 | 20 | 4. Run command 21 | $ go build . 22 | $ ./main 23 | 24 | Batch Consumer: Begin Batch 25 | Batch Consumer: {"message_id":0} 26 | Batch Consumer: {"message_id":1} 27 | Batch Consumer: {"message_id":2} 28 | Batch Consumer: End Batch 29 | Batch Consumer: Begin Batch 30 | Batch Consumer: {"message_id":3} 31 | Batch Consumer: End Batch 32 | Batch Consumer: Begin Batch 33 | Batch Consumer: {"message_id":4} 34 | Batch Consumer: {"message_id":5} 35 | Batch Consumer: {"message_id":6} 36 | Batch Consumer: End Batch 37 | Batch Consumer: Begin Batch 38 | Batch Consumer: {"message_id":7} 39 | Batch Consumer: {"message_id":8} 40 | Batch Consumer: End Batch 41 | Batch Consumer: Begin Batch 42 | Batch Consumer: {"message_id":9} 43 | Batch Consumer: End Batch 44 | 45 | 5. Run command to cleanup 46 | $ docker-compose down -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/03-batch-consumer-service/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | // IContext is the context for service 5 | type IContext interface { 6 | Log(message string) 7 | Param(name string) string 8 | Response(responseCode int, responseData interface{}) 9 | ReadInput() string 10 | ReadInputs() []string 11 | } 12 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/03-batch-consumer-service/context_consumer_batch.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import "fmt" 5 | 6 | // BatchConsumerContext implement IContext it is context for Consumer 7 | type BatchConsumerContext struct { 8 | ms *Microservice 9 | messages []string 10 | } 11 | 12 | // NewBatchConsumerContext is the constructor function for BatchConsumerContext 13 | func NewBatchConsumerContext(ms *Microservice, messages []string) *BatchConsumerContext { 14 | return &BatchConsumerContext{ 15 | ms: ms, 16 | messages: messages, 17 | } 18 | } 19 | 20 | // Log will log a message 21 | func (ctx *BatchConsumerContext) Log(message string) { 22 | fmt.Println("Batch Consumer: ", message) 23 | } 24 | 25 | // Param return parameter by name (empty in case of Consumer) 26 | func (ctx *BatchConsumerContext) Param(name string) string { 27 | return "" 28 | } 29 | 30 | // ReadInput return message (return empty in batch consumer) 31 | func (ctx *BatchConsumerContext) ReadInput() string { 32 | return "" 33 | } 34 | 35 | // ReadInputs return messages in batch 36 | func (ctx *BatchConsumerContext) ReadInputs() []string { 37 | return ctx.messages 38 | } 39 | 40 | // Response return response to client 41 | func (ctx *BatchConsumerContext) Response(responseCode int, responseData interface{}) { 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/03-batch-consumer-service/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | zookeeper: 4 | image: 3dsinteractive/zookeeper:3.0 5 | ports: 6 | - 2181:2181 7 | environment: 8 | - ALLOW_ANONYMOUS_LOGIN=yes 9 | kafka: 10 | image: 3dsinteractive/kafka:2.0-custom 11 | ports: 12 | - 9094:9094 13 | environment: 14 | - ALLOW_PLAINTEXT_LISTENER=yes 15 | - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 16 | - KAFKA_ADVERTISED_LISTENERS=INSIDE://:9092,OUTSIDE://localhost:9094 17 | - KAFKA_LISTENERS=INSIDE://:9092,OUTSIDE://:9094 18 | - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT 19 | - KAFKA_INTER_BROKER_LISTENER_NAME=INSIDE 20 | - KAFKA_DELETE_TOPIC_ENABLE=true 21 | - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true 22 | - KAFKA_NUM_NETWORK_THREADS=8 23 | - KAFKA_NUM_IO_THREADS=16 24 | depends_on: 25 | - zookeeper -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/03-batch-consumer-service/main.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ms := NewMicroservice() 10 | 11 | servers := "localhost:9094" 12 | topic := "when-citizen-has-registered-batch-" + randString() 13 | groupID := "validation-consumer" 14 | timeout := time.Duration(-1) 15 | batchSize := 3 16 | batchTimeout := time.Second * 5 17 | 18 | ms.ConsumeBatch(servers, topic, groupID, timeout, batchSize, batchTimeout, 19 | func(ctx IContext) error { 20 | msgs := ctx.ReadInputs() 21 | ctx.Log("Begin Batch") 22 | for _, msg := range msgs { 23 | ctx.Log(msg) 24 | } 25 | ctx.Log("End Batch") 26 | return nil 27 | }) 28 | 29 | prod := NewProducer(servers, ms) 30 | go func() { 31 | for i := 0; i < 10; i++ { 32 | prod.SendMessage(topic, "", map[string]interface{}{"message_id": i}) 33 | time.Sleep(time.Second) 34 | } 35 | 36 | // Wait for last batch then exit program 37 | time.Sleep(5 * time.Second) 38 | ms.Stop() 39 | }() 40 | 41 | defer ms.Cleanup() 42 | ms.Start() 43 | } 44 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/03-batch-consumer-service/util.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | ) 8 | 9 | func randString() string { 10 | i := rand.Int() 11 | return fmt.Sprintf("%d", i) 12 | } 13 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/04-scheduler-service/README: -------------------------------------------------------------------------------- 1 | Scheduler Service 2 | 3 | 0. Look at slide Scheduler Service 4 | 5 | 1. Open vscode at chapter02-microservices/2.2-microservices-types/04-scheduler-service 6 | 7 | 2. Run command 8 | $ go mod init automationworkshop/main 9 | go: creating new go.mod: module automationworkshop/main 10 | 11 | 3. Run command 12 | $ go build . 13 | $ ./main 14 | 15 | Scheduler: Tick at 21:40:45 16 | Scheduler: Tick at 21:40:46 17 | Scheduler: Tick at 21:40:47 18 | Scheduler: Tick at 21:40:48 19 | Scheduler: Tick at 21:40:49 20 | Scheduler: Tick at 21:40:50 21 | Scheduler: Tick at 21:40:51 22 | Scheduler: Tick at 21:40:52 23 | Scheduler: Tick at 21:40:53 24 | Scheduler: Tick at 21:40:54 25 | Scheduler: Tick at 21:40:55 26 | Scheduler: Tick at 21:40:56 27 | Scheduler: Tick at 21:40:57 28 | 29 | 4. Run command for cleanup 30 | $ docker-compose down 31 | 32 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/04-scheduler-service/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import "time" 5 | 6 | // IContext is the context for service 7 | type IContext interface { 8 | Log(message string) 9 | Param(name string) string 10 | Response(responseCode int, responseData interface{}) 11 | ReadInput() string 12 | ReadInputs() []string 13 | 14 | Now() time.Time 15 | } 16 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/04-scheduler-service/context_scheduler.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | // SchedulerContext implement IContext it is context for Consumer 10 | type SchedulerContext struct { 11 | ms *Microservice 12 | } 13 | 14 | // NewSchedulerContext is the constructor function for SchedulerContext 15 | func NewSchedulerContext(ms *Microservice) *SchedulerContext { 16 | return &SchedulerContext{ 17 | ms: ms, 18 | } 19 | } 20 | 21 | // Now return time.Now 22 | func (ctx *SchedulerContext) Now() time.Time { 23 | return time.Now() 24 | } 25 | 26 | // Log will log a message 27 | func (ctx *SchedulerContext) Log(message string) { 28 | fmt.Println("Scheduler: ", message) 29 | } 30 | 31 | // Param return parameter by name (empty in scheduler) 32 | func (ctx *SchedulerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // ReadInput return message (return empty in scheduler) 37 | func (ctx *SchedulerContext) ReadInput() string { 38 | return "" 39 | } 40 | 41 | // ReadInputs return messages in batch (return nil in scheduler) 42 | func (ctx *SchedulerContext) ReadInputs() []string { 43 | return nil 44 | } 45 | 46 | // Response return response to client 47 | func (ctx *SchedulerContext) Response(responseCode int, responseData interface{}) { 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/04-scheduler-service/main.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | ms := NewMicroservice() 11 | 12 | timer := 1 * time.Second 13 | exitScheduler := ms.Schedule(timer, func(ctx IContext) error { 14 | now := ctx.Now() 15 | ctx.Log(fmt.Sprintf("Tick at %s", now.Format("15:04:05"))) 16 | return nil 17 | }) 18 | 19 | defer func() { exitScheduler <- true }() 20 | defer ms.Cleanup() 21 | 22 | ms.Start() 23 | } 24 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/05-asynctask-service/README: -------------------------------------------------------------------------------- 1 | AsyncTask Service 2 | 3 | 0. Look at slide AsyncTask Service 4 | 5 | 1. Open vscode at chapter02-microservices/2.2-microservices-types/05-asynctask-service 6 | 7 | 2. Start kafka and zookeeper locally via docker 8 | $ docker-compose up -d 9 | * Wait until container is ready 10 | 11 | $ docker ps 12 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 13 | eecc315cc549 3dsinteractive/kafka:2.0 "/app-entrypoint.sh …" 2 minutes ago Up 2 minutes 9092/tcp, 0.0.0.0:9094->9094/tcp 02-consumer-service_kafka_1 14 | 67b5f2efaf0a 3dsinteractive/zookeeper:3.0 "/app-entrypoint.sh …" 2 minutes ago Up 2 minutes 2888/tcp, 0.0.0.0:2181->2181/tcp, 3888/tcp 02-consumer-service_zookeeper_1 15 | 16 | 3. Run command 17 | $ go mod init automationworkshop/main 18 | go: creating new go.mod: module automationworkshop/main 19 | 20 | 4. Run command 21 | $ go build . 22 | $ ./main 23 | 24 | 5. Open another terminal and run command 25 | curl -X POST "localhost:8080/citizen/register" -d '{"firstname":"chaiyapong"}' 26 | {"ref":"atask-xxxxxxxxxxx"} 27 | 28 | 6. Run command 29 | curl -X GET "localhost:8080/citizen/register?ref=atask-xxxxxxxxxxx" 30 | {"code":200,"data":{"id":"123"},"status":"success"} 31 | 32 | 7. Run command for cleanup 33 | $ docker-compose down -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/05-asynctask-service/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | // IContext is the context for service 5 | type IContext interface { 6 | Log(message string) 7 | Param(name string) string 8 | QueryParam(name string) string 9 | Response(responseCode int, responseData interface{}) 10 | ReadInput() string 11 | ReadInputs() []string 12 | 13 | // Dependency 14 | Cacher(server string) ICacher 15 | Producer(servers string) IProducer 16 | MQ(servers string) IMQ 17 | } 18 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/05-asynctask-service/context_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import "fmt" 5 | 6 | // ConsumerContext implement IContext it is context for Consumer 7 | type ConsumerContext struct { 8 | ms *Microservice 9 | message string 10 | } 11 | 12 | // NewConsumerContext is the constructor function for ConsumerContext 13 | func NewConsumerContext(ms *Microservice, message string) *ConsumerContext { 14 | return &ConsumerContext{ 15 | ms: ms, 16 | message: message, 17 | } 18 | } 19 | 20 | // Log will log a message 21 | func (ctx *ConsumerContext) Log(message string) { 22 | fmt.Println("Consumer: ", message) 23 | } 24 | 25 | // Param return parameter by name (empty in case of Consumer) 26 | func (ctx *ConsumerContext) Param(name string) string { 27 | return "" 28 | } 29 | 30 | // QueryParam return empty in consumer 31 | func (ctx *ConsumerContext) QueryParam(name string) string { 32 | return "" 33 | } 34 | 35 | // ReadInput return message 36 | func (ctx *ConsumerContext) ReadInput() string { 37 | return ctx.message 38 | } 39 | 40 | // ReadInputs return nil in case Consumer 41 | func (ctx *ConsumerContext) ReadInputs() []string { 42 | return nil 43 | } 44 | 45 | // Response return response to client 46 | func (ctx *ConsumerContext) Response(responseCode int, responseData interface{}) { 47 | return 48 | } 49 | 50 | // Cacher return cacher 51 | func (ctx *ConsumerContext) Cacher(server string) ICacher { 52 | return NewCacher(server) 53 | } 54 | 55 | // Producer return producer 56 | func (ctx *ConsumerContext) Producer(servers string) IProducer { 57 | return NewProducer(servers, ctx.ms) 58 | } 59 | 60 | // MQ return MQ 61 | func (ctx *ConsumerContext) MQ(servers string) IMQ { 62 | return NewMQ(servers, ctx.ms) 63 | } 64 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/05-asynctask-service/context_http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | 7 | "github.com/labstack/echo" 8 | ) 9 | 10 | // HTTPContext implement IContext it is context for HTTP 11 | type HTTPContext struct { 12 | ms *Microservice 13 | c echo.Context 14 | } 15 | 16 | // NewHTTPContext is the constructor function for HTTPContext 17 | func NewHTTPContext(ms *Microservice, c echo.Context) *HTTPContext { 18 | return &HTTPContext{ 19 | ms: ms, 20 | c: c, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *HTTPContext) Log(message string) { 26 | fmt.Println("HTTP: ", message) 27 | } 28 | 29 | // Param return parameter by name 30 | func (ctx *HTTPContext) Param(name string) string { 31 | return ctx.c.Param(name) 32 | } 33 | 34 | // QueryParam return query param 35 | func (ctx *HTTPContext) QueryParam(name string) string { 36 | return ctx.c.QueryParam(name) 37 | } 38 | 39 | // ReadInput read the request body and return it as string 40 | func (ctx *HTTPContext) ReadInput() string { 41 | body, err := ioutil.ReadAll(ctx.c.Request().Body) 42 | if err != nil { 43 | return "" 44 | } 45 | return string(body) 46 | } 47 | 48 | // ReadInputs return nil in HTTP Context 49 | func (ctx *HTTPContext) ReadInputs() []string { 50 | return nil 51 | } 52 | 53 | // Response return response to client 54 | func (ctx *HTTPContext) Response(responseCode int, responseData interface{}) { 55 | ctx.c.JSON(responseCode, responseData) 56 | } 57 | 58 | // Cacher return cacher 59 | func (ctx *HTTPContext) Cacher(server string) ICacher { 60 | return NewCacher(server) 61 | } 62 | 63 | // Producer return producer 64 | func (ctx *HTTPContext) Producer(servers string) IProducer { 65 | return NewProducer(servers, ctx.ms) 66 | } 67 | 68 | // MQ return MQ 69 | func (ctx *HTTPContext) MQ(servers string) IMQ { 70 | return NewMQ(servers, ctx.ms) 71 | } 72 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/05-asynctask-service/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | zookeeper: 4 | image: 3dsinteractive/zookeeper:3.0 5 | ports: 6 | - 2181:2181 7 | environment: 8 | - ALLOW_ANONYMOUS_LOGIN=yes 9 | kafka: 10 | image: 3dsinteractive/kafka:2.0-custom 11 | ports: 12 | - 9094:9094 13 | environment: 14 | - ALLOW_PLAINTEXT_LISTENER=yes 15 | - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 16 | - KAFKA_ADVERTISED_LISTENERS=INSIDE://:9092,OUTSIDE://localhost:9094 17 | - KAFKA_LISTENERS=INSIDE://:9092,OUTSIDE://:9094 18 | - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT 19 | - KAFKA_INTER_BROKER_LISTENER_NAME=INSIDE 20 | - KAFKA_DELETE_TOPIC_ENABLE=true 21 | - KAFKA_AUTO_CREATE_TOPICS_ENABLE=true 22 | - KAFKA_NUM_NETWORK_THREADS=8 23 | - KAFKA_NUM_IO_THREADS=16 24 | depends_on: 25 | - zookeeper 26 | redis: 27 | image: 3dsinteractive/redis:4.0 28 | environment: 29 | - ALLOW_EMPTY_PASSWORD=yes 30 | ports: 31 | - 6379:6379 -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/05-asynctask-service/main.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import "net/http" 5 | 6 | func main() { 7 | ms := NewMicroservice() 8 | 9 | cacheServer := "localhost:6379" 10 | mqServers := "localhost:9094" 11 | 12 | ms.AsyncPOST("/citizen/register", cacheServer, mqServers, func(ctx IContext) error { 13 | ctx.Log(ctx.ReadInput()) 14 | res := map[string]interface{}{ 15 | "id": "123", 16 | } 17 | ctx.Response(http.StatusOK, res) 18 | return nil 19 | }) 20 | 21 | defer ms.Cleanup() 22 | ms.Start() 23 | } 24 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/05-asynctask-service/util.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "math/rand" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var specialCharsRegex = regexp.MustCompile("[^a-zA-Z0-9]+") 13 | 14 | func randString() string { 15 | i := rand.Int() 16 | return fmt.Sprintf("%d", i) 17 | } 18 | 19 | func escapeName(tokens ...string) string { 20 | // Any name rules 21 | // - Lowercase only (for consistency) 22 | // - . (dot), _ (underscore), - (minus) can be used 23 | // - Max length = 250 24 | var b bytes.Buffer 25 | 26 | // Name result must be token1-token2-token3-token4 without special characters 27 | for i, token := range tokens { 28 | if len(token) == 0 { 29 | continue 30 | } 31 | 32 | token = strings.ToLower(token) 33 | 34 | cleanToken := specialCharsRegex.ReplaceAllString(token, "-") 35 | if i != 0 { 36 | b.WriteString("-") 37 | } 38 | b.WriteString(cleanToken) 39 | } 40 | 41 | name := b.String() 42 | // - Cannot start with -, _, + 43 | for true { 44 | if len(name) == 0 || name[0] != '-' { 45 | break 46 | } 47 | name = name[1:] 48 | } 49 | 50 | // - Cannot be longer than 250 characters (max len) 51 | if len(name) > 250 { 52 | name = name[0:250] 53 | } 54 | 55 | return name 56 | } 57 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/06-paralleltask-service/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | // IContext is the context for service 5 | type IContext interface { 6 | Log(message string) 7 | Param(name string) string 8 | QueryParam(name string) string 9 | Response(responseCode int, responseData interface{}) 10 | ReadInput() string 11 | ReadInputs() []string 12 | 13 | // Dependency 14 | Cacher(server string) ICacher 15 | Producer(servers string) IProducer 16 | MQ(servers string) IMQ 17 | } 18 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/06-paralleltask-service/context_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // ConsumerContext implement IContext it is context for Consumer 11 | type ConsumerContext struct { 12 | ms *Microservice 13 | message string 14 | } 15 | 16 | // NewConsumerContext is the constructor function for ConsumerContext 17 | func NewConsumerContext(ms *Microservice, message string) *ConsumerContext { 18 | return &ConsumerContext{ 19 | ms: ms, 20 | message: message, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *ConsumerContext) Log(message string) { 26 | _, fn, line, _ := runtime.Caller(1) 27 | fns := strings.Split(fn, "/") 28 | fmt.Println("Consumer:", fns[len(fns)-1], line, message) 29 | } 30 | 31 | // Param return parameter by name (empty in case of Consumer) 32 | func (ctx *ConsumerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // QueryParam return empty in consumer 37 | func (ctx *ConsumerContext) QueryParam(name string) string { 38 | return "" 39 | } 40 | 41 | // ReadInput return message 42 | func (ctx *ConsumerContext) ReadInput() string { 43 | return ctx.message 44 | } 45 | 46 | // ReadInputs return nil in case Consumer 47 | func (ctx *ConsumerContext) ReadInputs() []string { 48 | return nil 49 | } 50 | 51 | // Response return response to client 52 | func (ctx *ConsumerContext) Response(responseCode int, responseData interface{}) { 53 | return 54 | } 55 | 56 | // Cacher return cacher 57 | func (ctx *ConsumerContext) Cacher(server string) ICacher { 58 | return NewCacher(server) 59 | } 60 | 61 | // Producer return producer 62 | func (ctx *ConsumerContext) Producer(servers string) IProducer { 63 | return NewProducer(servers, ctx.ms) 64 | } 65 | 66 | // MQ return MQ 67 | func (ctx *ConsumerContext) MQ(servers string) IMQ { 68 | return NewMQ(servers, ctx.ms) 69 | } 70 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/06-paralleltask-service/context_http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "runtime" 7 | "strings" 8 | 9 | "github.com/labstack/echo" 10 | ) 11 | 12 | // HTTPContext implement IContext it is context for HTTP 13 | type HTTPContext struct { 14 | ms *Microservice 15 | c echo.Context 16 | } 17 | 18 | // NewHTTPContext is the constructor function for HTTPContext 19 | func NewHTTPContext(ms *Microservice, c echo.Context) *HTTPContext { 20 | return &HTTPContext{ 21 | ms: ms, 22 | c: c, 23 | } 24 | } 25 | 26 | // Log will log a message 27 | func (ctx *HTTPContext) Log(message string) { 28 | _, fn, line, _ := runtime.Caller(1) 29 | fns := strings.Split(fn, "/") 30 | fmt.Println("HTTP:", fns[len(fns)-1], line, message) 31 | } 32 | 33 | // Param return parameter by name 34 | func (ctx *HTTPContext) Param(name string) string { 35 | return ctx.c.Param(name) 36 | } 37 | 38 | // QueryParam return query param 39 | func (ctx *HTTPContext) QueryParam(name string) string { 40 | return ctx.c.QueryParam(name) 41 | } 42 | 43 | // ReadInput read the request body and return it as string 44 | func (ctx *HTTPContext) ReadInput() string { 45 | body, err := ioutil.ReadAll(ctx.c.Request().Body) 46 | if err != nil { 47 | return "" 48 | } 49 | return string(body) 50 | } 51 | 52 | // ReadInputs return nil in HTTP Context 53 | func (ctx *HTTPContext) ReadInputs() []string { 54 | return nil 55 | } 56 | 57 | // Response return response to client 58 | func (ctx *HTTPContext) Response(responseCode int, responseData interface{}) { 59 | ctx.c.JSON(responseCode, responseData) 60 | } 61 | 62 | // Cacher return cacher 63 | func (ctx *HTTPContext) Cacher(server string) ICacher { 64 | return NewCacher(server) 65 | } 66 | 67 | // Producer return producer 68 | func (ctx *HTTPContext) Producer(servers string) IProducer { 69 | return NewProducer(servers, ctx.ms) 70 | } 71 | 72 | // MQ return MQ 73 | func (ctx *HTTPContext) MQ(servers string) IMQ { 74 | return NewMQ(servers, ctx.ms) 75 | } 76 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/06-paralleltask-service/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | zookeeper: 4 | image: 3dsinteractive/zookeeper:3.0 5 | ports: 6 | - 2181:2181 7 | environment: 8 | - ALLOW_ANONYMOUS_LOGIN=yes 9 | kafka: 10 | image: 3dsinteractive/kafka:2.0-custom 11 | ports: 12 | - 9094:9094 13 | environment: 14 | - ALLOW_PLAINTEXT_LISTENER=yes 15 | - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 16 | - KAFKA_ADVERTISED_LISTENERS=INSIDE://:9092,OUTSIDE://localhost:9094 17 | - KAFKA_LISTENERS=INSIDE://:9092,OUTSIDE://:9094 18 | - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT 19 | - KAFKA_INTER_BROKER_LISTENER_NAME=INSIDE 20 | - KAFKA_DELETE_TOPIC_ENABLE=true 21 | - KAFKA_AUTO_CREATE_TOPICS_ENABLE=true 22 | - KAFKA_NUM_NETWORK_THREADS=8 23 | - KAFKA_NUM_IO_THREADS=16 24 | depends_on: 25 | - zookeeper 26 | redis: 27 | image: 3dsinteractive/redis:4.0 28 | environment: 29 | - ALLOW_EMPTY_PASSWORD=yes 30 | ports: 31 | - 6379:6379 -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/06-paralleltask-service/main.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "math/rand" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | ms := NewMicroservice() 12 | 13 | cacheServer := "localhost:6379" 14 | mqServers := "localhost:9094" 15 | 16 | // 1. Start PTask endpoint 17 | ms.PTaskEndpoint("/citizen/batch", cacheServer, mqServers) 18 | 19 | // 2. Start 2 worker nodes 20 | for i := 0; i < 2; i++ { 21 | ms.PTaskWorkerNode("/citizen/batch", cacheServer, mqServers, func(ctx IContext) error { 22 | ctx.Log(ctx.ReadInput()) 23 | resStr := randString() 24 | res := map[string]interface{}{ 25 | "result": resStr, 26 | } 27 | n := rand.Intn(5) 28 | time.Sleep(time.Duration(n) * time.Second) 29 | ctx.Response(http.StatusOK, res) 30 | return nil 31 | }) 32 | } 33 | 34 | defer ms.Cleanup() 35 | ms.Start() 36 | } 37 | -------------------------------------------------------------------------------- /chapter02-microservices/2.2-microservices-types/06-paralleltask-service/util.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "math/rand" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var specialCharsRegex = regexp.MustCompile("[^a-zA-Z0-9]+") 13 | 14 | func randString() string { 15 | i := rand.Int() 16 | return fmt.Sprintf("%d", i) 17 | } 18 | 19 | func escapeName(tokens ...string) string { 20 | // Any name rules 21 | // - Lowercase only (for consistency) 22 | // - . (dot), _ (underscore), - (minus) can be used 23 | // - Max length = 250 24 | var b bytes.Buffer 25 | 26 | // Name result must be token1-token2-token3-token4 without special characters 27 | for i, token := range tokens { 28 | if len(token) == 0 { 29 | continue 30 | } 31 | 32 | token = strings.ToLower(token) 33 | 34 | cleanToken := specialCharsRegex.ReplaceAllString(token, "-") 35 | if i != 0 { 36 | b.WriteString("-") 37 | } 38 | b.WriteString(cleanToken) 39 | } 40 | 41 | name := b.String() 42 | // - Cannot start with -, _, + 43 | for true { 44 | if len(name) == 0 || name[0] != '-' { 45 | break 46 | } 47 | name = name[1:] 48 | } 49 | 50 | // - Cannot be longer than 250 characters (max len) 51 | if len(name) > 250 { 52 | name = name[0:250] 53 | } 54 | 55 | return name 56 | } 57 | -------------------------------------------------------------------------------- /chapter02-microservices/2.3-service-startup-teardown/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | // IContext is the context for service 5 | type IContext interface { 6 | Log(message string) 7 | Param(name string) string 8 | QueryParam(name string) string 9 | Response(responseCode int, responseData interface{}) 10 | ReadInput() string 11 | ReadInputs() []string 12 | 13 | // Dependency 14 | Cacher(server string) ICacher 15 | Producer(servers string) IProducer 16 | MQ(servers string) IMQ 17 | } 18 | -------------------------------------------------------------------------------- /chapter02-microservices/2.3-service-startup-teardown/context_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // ConsumerContext implement IContext it is context for Consumer 11 | type ConsumerContext struct { 12 | ms *Microservice 13 | message string 14 | } 15 | 16 | // NewConsumerContext is the constructor function for ConsumerContext 17 | func NewConsumerContext(ms *Microservice, message string) *ConsumerContext { 18 | return &ConsumerContext{ 19 | ms: ms, 20 | message: message, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *ConsumerContext) Log(message string) { 26 | _, fn, line, _ := runtime.Caller(1) 27 | fns := strings.Split(fn, "/") 28 | fmt.Println("Consumer:", fns[len(fns)-1], line, message) 29 | } 30 | 31 | // Param return parameter by name (empty in case of Consumer) 32 | func (ctx *ConsumerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // QueryParam return empty in consumer 37 | func (ctx *ConsumerContext) QueryParam(name string) string { 38 | return "" 39 | } 40 | 41 | // ReadInput return message 42 | func (ctx *ConsumerContext) ReadInput() string { 43 | return ctx.message 44 | } 45 | 46 | // ReadInputs return nil in case Consumer 47 | func (ctx *ConsumerContext) ReadInputs() []string { 48 | return nil 49 | } 50 | 51 | // Response return response to client 52 | func (ctx *ConsumerContext) Response(responseCode int, responseData interface{}) { 53 | return 54 | } 55 | 56 | // Cacher return cacher 57 | func (ctx *ConsumerContext) Cacher(server string) ICacher { 58 | return ctx.ms.getCacher(server) 59 | } 60 | 61 | // Producer return producer 62 | func (ctx *ConsumerContext) Producer(servers string) IProducer { 63 | return ctx.ms.getProducer(servers) 64 | } 65 | 66 | // MQ return MQ 67 | func (ctx *ConsumerContext) MQ(servers string) IMQ { 68 | return NewMQ(servers, ctx.ms) 69 | } 70 | -------------------------------------------------------------------------------- /chapter02-microservices/2.3-service-startup-teardown/context_consumer_batch.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // BatchConsumerContext implement IContext it is context for Consumer 11 | type BatchConsumerContext struct { 12 | ms *Microservice 13 | messages []string 14 | } 15 | 16 | // NewBatchConsumerContext is the constructor function for BatchConsumerContext 17 | func NewBatchConsumerContext(ms *Microservice, messages []string) *BatchConsumerContext { 18 | return &BatchConsumerContext{ 19 | ms: ms, 20 | messages: messages, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *BatchConsumerContext) Log(message string) { 26 | _, fn, line, _ := runtime.Caller(1) 27 | fns := strings.Split(fn, "/") 28 | fmt.Println("Batch Consumer:", fns[len(fns)-1], line, message) 29 | } 30 | 31 | // Param return parameter by name (empty in case of Consumer) 32 | func (ctx *BatchConsumerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // QueryParam return empty in consumer batch 37 | func (ctx *BatchConsumerContext) QueryParam(name string) string { 38 | return "" 39 | } 40 | 41 | // ReadInput return message (return empty in batch consumer) 42 | func (ctx *BatchConsumerContext) ReadInput() string { 43 | return "" 44 | } 45 | 46 | // ReadInputs return messages in batch 47 | func (ctx *BatchConsumerContext) ReadInputs() []string { 48 | return ctx.messages 49 | } 50 | 51 | // Response return response to client 52 | func (ctx *BatchConsumerContext) Response(responseCode int, responseData interface{}) { 53 | return 54 | } 55 | 56 | // Cacher return cacher 57 | func (ctx *BatchConsumerContext) Cacher(server string) ICacher { 58 | return ctx.ms.getCacher(server) 59 | } 60 | 61 | // Producer return producer 62 | func (ctx *BatchConsumerContext) Producer(servers string) IProducer { 63 | return ctx.ms.getProducer(servers) 64 | } 65 | 66 | // MQ return MQ 67 | func (ctx *BatchConsumerContext) MQ(servers string) IMQ { 68 | return NewMQ(servers, ctx.ms) 69 | } 70 | -------------------------------------------------------------------------------- /chapter02-microservices/2.3-service-startup-teardown/context_http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "runtime" 7 | "strings" 8 | 9 | "github.com/labstack/echo" 10 | ) 11 | 12 | // HTTPContext implement IContext it is context for HTTP 13 | type HTTPContext struct { 14 | ms *Microservice 15 | c echo.Context 16 | } 17 | 18 | // NewHTTPContext is the constructor function for HTTPContext 19 | func NewHTTPContext(ms *Microservice, c echo.Context) *HTTPContext { 20 | return &HTTPContext{ 21 | ms: ms, 22 | c: c, 23 | } 24 | } 25 | 26 | // Log will log a message 27 | func (ctx *HTTPContext) Log(message string) { 28 | _, fn, line, _ := runtime.Caller(1) 29 | fns := strings.Split(fn, "/") 30 | fmt.Println("HTTP:", fns[len(fns)-1], line, message) 31 | } 32 | 33 | // Param return parameter by name 34 | func (ctx *HTTPContext) Param(name string) string { 35 | return ctx.c.Param(name) 36 | } 37 | 38 | // QueryParam return query param 39 | func (ctx *HTTPContext) QueryParam(name string) string { 40 | return ctx.c.QueryParam(name) 41 | } 42 | 43 | // ReadInput read the request body and return it as string 44 | func (ctx *HTTPContext) ReadInput() string { 45 | body, err := ioutil.ReadAll(ctx.c.Request().Body) 46 | if err != nil { 47 | return "" 48 | } 49 | return string(body) 50 | } 51 | 52 | // ReadInputs return nil in HTTP Context 53 | func (ctx *HTTPContext) ReadInputs() []string { 54 | return nil 55 | } 56 | 57 | // Response return response to client 58 | func (ctx *HTTPContext) Response(responseCode int, responseData interface{}) { 59 | ctx.c.JSON(responseCode, responseData) 60 | } 61 | 62 | // Cacher return cacher 63 | func (ctx *HTTPContext) Cacher(server string) ICacher { 64 | return ctx.ms.getCacher(server) 65 | } 66 | 67 | // Producer return producer 68 | func (ctx *HTTPContext) Producer(servers string) IProducer { 69 | return ctx.ms.getProducer(servers) 70 | } 71 | 72 | // MQ return MQ 73 | func (ctx *HTTPContext) MQ(servers string) IMQ { 74 | return NewMQ(servers, ctx.ms) 75 | } 76 | -------------------------------------------------------------------------------- /chapter02-microservices/2.3-service-startup-teardown/context_scheduler.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // SchedulerContext implement IContext it is context for Consumer 11 | type SchedulerContext struct { 12 | ms *Microservice 13 | } 14 | 15 | // NewSchedulerContext is the constructor function for SchedulerContext 16 | func NewSchedulerContext(ms *Microservice) *SchedulerContext { 17 | return &SchedulerContext{ 18 | ms: ms, 19 | } 20 | } 21 | 22 | // Log will log a message 23 | func (ctx *SchedulerContext) Log(message string) { 24 | _, fn, line, _ := runtime.Caller(1) 25 | fns := strings.Split(fn, "/") 26 | fmt.Println("Scheduler:", fns[len(fns)-1], line, message) 27 | } 28 | 29 | // Param return parameter by name (empty in scheduler) 30 | func (ctx *SchedulerContext) Param(name string) string { 31 | return "" 32 | } 33 | 34 | // QueryParam return empty in scheduler 35 | func (ctx *SchedulerContext) QueryParam(name string) string { 36 | return "" 37 | } 38 | 39 | // ReadInput return message (return empty in scheduler) 40 | func (ctx *SchedulerContext) ReadInput() string { 41 | return "" 42 | } 43 | 44 | // ReadInputs return messages in batch (return nil in scheduler) 45 | func (ctx *SchedulerContext) ReadInputs() []string { 46 | return nil 47 | } 48 | 49 | // Response return response to client 50 | func (ctx *SchedulerContext) Response(responseCode int, responseData interface{}) { 51 | return 52 | } 53 | 54 | // Cacher return cacher 55 | func (ctx *SchedulerContext) Cacher(server string) ICacher { 56 | return ctx.ms.getCacher(server) 57 | } 58 | 59 | // Producer return producer 60 | func (ctx *SchedulerContext) Producer(servers string) IProducer { 61 | return ctx.ms.getProducer(servers) 62 | } 63 | 64 | // MQ return MQ 65 | func (ctx *SchedulerContext) MQ(servers string) IMQ { 66 | return NewMQ(servers, ctx.ms) 67 | } 68 | -------------------------------------------------------------------------------- /chapter02-microservices/2.3-service-startup-teardown/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | zookeeper: 4 | image: 3dsinteractive/zookeeper:3.0 5 | ports: 6 | - 2181:2181 7 | environment: 8 | - ALLOW_ANONYMOUS_LOGIN=yes 9 | kafka: 10 | image: 3dsinteractive/kafka:2.0-custom 11 | ports: 12 | - 9094:9094 13 | environment: 14 | - ALLOW_PLAINTEXT_LISTENER=yes 15 | - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 16 | - KAFKA_ADVERTISED_LISTENERS=INSIDE://:9092,OUTSIDE://localhost:9094 17 | - KAFKA_LISTENERS=INSIDE://:9092,OUTSIDE://:9094 18 | - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT 19 | - KAFKA_INTER_BROKER_LISTENER_NAME=INSIDE 20 | - KAFKA_DELETE_TOPIC_ENABLE=true 21 | - KAFKA_AUTO_CREATE_TOPICS_ENABLE=true 22 | - KAFKA_NUM_NETWORK_THREADS=8 23 | - KAFKA_NUM_IO_THREADS=16 24 | depends_on: 25 | - zookeeper 26 | redis: 27 | image: 3dsinteractive/redis:4.0 28 | environment: 29 | - ALLOW_EMPTY_PASSWORD=yes 30 | ports: 31 | - 6379:6379 -------------------------------------------------------------------------------- /chapter02-microservices/2.3-service-startup-teardown/util.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "math/rand" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var specialCharsRegex = regexp.MustCompile("[^a-zA-Z0-9]+") 13 | 14 | func randString() string { 15 | i := rand.Int() 16 | return fmt.Sprintf("%d", i) 17 | } 18 | 19 | func escapeName(tokens ...string) string { 20 | // Any name rules 21 | // - Lowercase only (for consistency) 22 | // - . (dot), _ (underscore), - (minus) can be used 23 | // - Max length = 250 24 | var b bytes.Buffer 25 | 26 | // Name result must be token1-token2-token3-token4 without special characters 27 | for i, token := range tokens { 28 | if len(token) == 0 { 29 | continue 30 | } 31 | 32 | token = strings.ToLower(token) 33 | 34 | cleanToken := specialCharsRegex.ReplaceAllString(token, "-") 35 | if i != 0 { 36 | b.WriteString("-") 37 | } 38 | b.WriteString(cleanToken) 39 | } 40 | 41 | name := b.String() 42 | // - Cannot start with -, _, + 43 | for true { 44 | if len(name) == 0 || name[0] != '-' { 45 | break 46 | } 47 | name = name[1:] 48 | } 49 | 50 | // - Cannot be longer than 250 characters (max len) 51 | if len(name) > 250 { 52 | name = name[0:250] 53 | } 54 | 55 | return name 56 | } 57 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 3dsinteractive/golang:1.14-alpine3.9-librdfkafka1.4.0 2 | 3 | # 1. Add all go files and build 4 | WORKDIR /go/src/bitbucket.org/automationworkshop/main 5 | ADD . /go/src/bitbucket.org/automationworkshop/main 6 | RUN go build -mod vendor -i -tags "musl static_all" . 7 | 8 | # ================ 9 | FROM 3dsinteractive/alpine:3.9 10 | 11 | # 2. Use multi stage docker file, and copy executable file from previous stage 12 | COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 13 | COPY --from=0 /go/src/bitbucket.org/automationworkshop/main/main /main 14 | 15 | # 3. Add entrypoint.sh (The file that will use as entrypoint when run go program) 16 | ADD ./entrypoint.sh /entrypoint.sh 17 | 18 | # 4. Create user 1001 (It is my practice to always use user with id 1001) 19 | RUN adduser -u 1001 -D -s /bin/sh -G ping 1001 20 | RUN chown 1001:1001 /entrypoint.sh 21 | RUN chown 1001:1001 /main 22 | 23 | # 5. Make entrypoint.sh and main executable 24 | RUN chmod +x /entrypoint.sh 25 | RUN chmod +x /main 26 | 27 | # 6. Set default user 28 | USER 1001 29 | 30 | # 7. Expose port 8080 31 | EXPOSE 8080 32 | 33 | # 8. Start program by run entrypoint.sh 34 | ENTRYPOINT ["/entrypoint.sh"] 35 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/README: -------------------------------------------------------------------------------- 1 | Deploy Service 2 | 3 | 0. Look into the slides about Deploy Service 4 | 5 | 1. Open vscode at chapter03-deploy-deploy-services/3.1-deploy-services 6 | 7 | 2. Register for https://hub.docker.com 8 | 9 | 3. Create public repository call [your-docker-repository-name]/automation-technology 10 | 11 | 4. Make sure docker is running (Check the docker icon in task bar) 12 | 13 | 5. Make sure you are logged in with your docker account 14 | $ docker login 15 | [username] 16 | [password] 17 | 18 | 6. Update file deploy.sh change 19 | DOCKER_REPOSITORY= 20 | to 21 | DOCKER_REPOSITORY=[your-docker-repository-name] 22 | 23 | 7. Run command (deploy.sh will be the file used to build your project, especially when it integrate with ci/cd) 24 | $ ./deploy.sh 25 | This will build the Dockerfile and push image to [your-docker-repository-name]/automation-technology 26 | 27 | 8. Explain deploy.sh (Each comments will explain itself) 28 | 29 | 9. Explain Dockerfile 30 | 31 | 10. Explain entrypoint.sh 32 | 33 | 11. Run command 34 | $ cd k8s 35 | 36 | 12. Run command 37 | $ kubectl apply -f . 38 | 39 | 13. Run command 40 | $ kubectl get po -n deploy-service 41 | register-api-758f494ccd-7xqmx 1/1 Running 0 109s 42 | register-api-758f494ccd-gbtdg 1/1 Running 0 109s 43 | 44 | 14. Run command 45 | $ kubectl get svc -n deploy-service 46 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 47 | register-api ClusterIP 10.103.76.235 8080/TCP 2m38s 48 | 49 | 15. Run command 50 | $ kubectl get ing -n deploy-service 51 | NAME CLASS HOSTS ADDRESS PORTS AGE 52 | ingress kubernetes.docker.internal localhost 80 91s 53 | 54 | 16. Run command 55 | $ curl -X POST "http://kubernetes.docker.internal/citizen" 56 | {"status":"success"} 57 | 58 | 17. Run command 59 | $ curl -X PUT "http://kubernetes.docker.internal/citizen/123" 60 | {"id":"123"} 61 | 62 | 18. Run command 63 | $ curl -X GET "http://kubernetes.docker.internal/citizen/123?page=2" 64 | {"id":"123","page":"2"} 65 | 66 | 19. Run command 67 | $ curl -X DELETE "http://kubernetes.docker.internal/citizen/123" 68 | {"status":"success"} 69 | 70 | 20. Run command 71 | $ kubectl delete ns deploy-service -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | // IContext is the context for service 5 | type IContext interface { 6 | Log(message string) 7 | Param(name string) string 8 | QueryParam(name string) string 9 | Response(responseCode int, responseData interface{}) 10 | ReadInput() string 11 | ReadInputs() []string 12 | 13 | // Dependency 14 | Cacher(server string) ICacher 15 | Producer(servers string) IProducer 16 | MQ(servers string) IMQ 17 | } 18 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/context_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // ConsumerContext implement IContext it is context for Consumer 11 | type ConsumerContext struct { 12 | ms *Microservice 13 | message string 14 | } 15 | 16 | // NewConsumerContext is the constructor function for ConsumerContext 17 | func NewConsumerContext(ms *Microservice, message string) *ConsumerContext { 18 | return &ConsumerContext{ 19 | ms: ms, 20 | message: message, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *ConsumerContext) Log(message string) { 26 | _, fn, line, _ := runtime.Caller(1) 27 | fns := strings.Split(fn, "/") 28 | fmt.Println("Consumer:", fns[len(fns)-1], line, message) 29 | } 30 | 31 | // Param return parameter by name (empty in case of Consumer) 32 | func (ctx *ConsumerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // QueryParam return empty in consumer 37 | func (ctx *ConsumerContext) QueryParam(name string) string { 38 | return "" 39 | } 40 | 41 | // ReadInput return message 42 | func (ctx *ConsumerContext) ReadInput() string { 43 | return ctx.message 44 | } 45 | 46 | // ReadInputs return nil in case Consumer 47 | func (ctx *ConsumerContext) ReadInputs() []string { 48 | return nil 49 | } 50 | 51 | // Response return response to client 52 | func (ctx *ConsumerContext) Response(responseCode int, responseData interface{}) { 53 | return 54 | } 55 | 56 | // Cacher return cacher 57 | func (ctx *ConsumerContext) Cacher(server string) ICacher { 58 | return ctx.ms.getCacher(server) 59 | } 60 | 61 | // Producer return producer 62 | func (ctx *ConsumerContext) Producer(servers string) IProducer { 63 | return ctx.ms.getProducer(servers) 64 | } 65 | 66 | // MQ return MQ 67 | func (ctx *ConsumerContext) MQ(servers string) IMQ { 68 | return NewMQ(servers, ctx.ms) 69 | } 70 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/context_consumer_batch.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // BatchConsumerContext implement IContext it is context for Consumer 11 | type BatchConsumerContext struct { 12 | ms *Microservice 13 | messages []string 14 | } 15 | 16 | // NewBatchConsumerContext is the constructor function for BatchConsumerContext 17 | func NewBatchConsumerContext(ms *Microservice, messages []string) *BatchConsumerContext { 18 | return &BatchConsumerContext{ 19 | ms: ms, 20 | messages: messages, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *BatchConsumerContext) Log(message string) { 26 | _, fn, line, _ := runtime.Caller(1) 27 | fns := strings.Split(fn, "/") 28 | fmt.Println("Batch Consumer:", fns[len(fns)-1], line, message) 29 | } 30 | 31 | // Param return parameter by name (empty in case of Consumer) 32 | func (ctx *BatchConsumerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // QueryParam return empty in consumer batch 37 | func (ctx *BatchConsumerContext) QueryParam(name string) string { 38 | return "" 39 | } 40 | 41 | // ReadInput return message (return empty in batch consumer) 42 | func (ctx *BatchConsumerContext) ReadInput() string { 43 | return "" 44 | } 45 | 46 | // ReadInputs return messages in batch 47 | func (ctx *BatchConsumerContext) ReadInputs() []string { 48 | return ctx.messages 49 | } 50 | 51 | // Response return response to client 52 | func (ctx *BatchConsumerContext) Response(responseCode int, responseData interface{}) { 53 | return 54 | } 55 | 56 | // Cacher return cacher 57 | func (ctx *BatchConsumerContext) Cacher(server string) ICacher { 58 | return ctx.ms.getCacher(server) 59 | } 60 | 61 | // Producer return producer 62 | func (ctx *BatchConsumerContext) Producer(servers string) IProducer { 63 | return ctx.ms.getProducer(servers) 64 | } 65 | 66 | // MQ return MQ 67 | func (ctx *BatchConsumerContext) MQ(servers string) IMQ { 68 | return NewMQ(servers, ctx.ms) 69 | } 70 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/context_http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "runtime" 7 | "strings" 8 | 9 | "github.com/labstack/echo" 10 | ) 11 | 12 | // HTTPContext implement IContext it is context for HTTP 13 | type HTTPContext struct { 14 | ms *Microservice 15 | c echo.Context 16 | } 17 | 18 | // NewHTTPContext is the constructor function for HTTPContext 19 | func NewHTTPContext(ms *Microservice, c echo.Context) *HTTPContext { 20 | return &HTTPContext{ 21 | ms: ms, 22 | c: c, 23 | } 24 | } 25 | 26 | // Log will log a message 27 | func (ctx *HTTPContext) Log(message string) { 28 | _, fn, line, _ := runtime.Caller(1) 29 | fns := strings.Split(fn, "/") 30 | fmt.Println("HTTP:", fns[len(fns)-1], line, message) 31 | } 32 | 33 | // Param return parameter by name 34 | func (ctx *HTTPContext) Param(name string) string { 35 | return ctx.c.Param(name) 36 | } 37 | 38 | // QueryParam return query param 39 | func (ctx *HTTPContext) QueryParam(name string) string { 40 | return ctx.c.QueryParam(name) 41 | } 42 | 43 | // ReadInput read the request body and return it as string 44 | func (ctx *HTTPContext) ReadInput() string { 45 | body, err := ioutil.ReadAll(ctx.c.Request().Body) 46 | if err != nil { 47 | return "" 48 | } 49 | return string(body) 50 | } 51 | 52 | // ReadInputs return nil in HTTP Context 53 | func (ctx *HTTPContext) ReadInputs() []string { 54 | return nil 55 | } 56 | 57 | // Response return response to client 58 | func (ctx *HTTPContext) Response(responseCode int, responseData interface{}) { 59 | ctx.c.JSON(responseCode, responseData) 60 | } 61 | 62 | // Cacher return cacher 63 | func (ctx *HTTPContext) Cacher(server string) ICacher { 64 | return ctx.ms.getCacher(server) 65 | } 66 | 67 | // Producer return producer 68 | func (ctx *HTTPContext) Producer(servers string) IProducer { 69 | return ctx.ms.getProducer(servers) 70 | } 71 | 72 | // MQ return MQ 73 | func (ctx *HTTPContext) MQ(servers string) IMQ { 74 | return NewMQ(servers, ctx.ms) 75 | } 76 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/context_scheduler.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // SchedulerContext implement IContext it is context for Consumer 11 | type SchedulerContext struct { 12 | ms *Microservice 13 | } 14 | 15 | // NewSchedulerContext is the constructor function for SchedulerContext 16 | func NewSchedulerContext(ms *Microservice) *SchedulerContext { 17 | return &SchedulerContext{ 18 | ms: ms, 19 | } 20 | } 21 | 22 | // Log will log a message 23 | func (ctx *SchedulerContext) Log(message string) { 24 | _, fn, line, _ := runtime.Caller(1) 25 | fns := strings.Split(fn, "/") 26 | fmt.Println("Scheduler:", fns[len(fns)-1], line, message) 27 | } 28 | 29 | // Param return parameter by name (empty in scheduler) 30 | func (ctx *SchedulerContext) Param(name string) string { 31 | return "" 32 | } 33 | 34 | // QueryParam return empty in scheduler 35 | func (ctx *SchedulerContext) QueryParam(name string) string { 36 | return "" 37 | } 38 | 39 | // ReadInput return message (return empty in scheduler) 40 | func (ctx *SchedulerContext) ReadInput() string { 41 | return "" 42 | } 43 | 44 | // ReadInputs return messages in batch (return nil in scheduler) 45 | func (ctx *SchedulerContext) ReadInputs() []string { 46 | return nil 47 | } 48 | 49 | // Response return response to client 50 | func (ctx *SchedulerContext) Response(responseCode int, responseData interface{}) { 51 | return 52 | } 53 | 54 | // Cacher return cacher 55 | func (ctx *SchedulerContext) Cacher(server string) ICacher { 56 | return ctx.ms.getCacher(server) 57 | } 58 | 59 | // Producer return producer 60 | func (ctx *SchedulerContext) Producer(servers string) IProducer { 61 | return ctx.ms.getProducer(servers) 62 | } 63 | 64 | // MQ return MQ 65 | func (ctx *SchedulerContext) MQ(servers string) IMQ { 66 | return NewMQ(servers, ctx.ms) 67 | } 68 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1. Format of docker image is $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP 4 | # for example 3dsinteractive/automation-technology:prd-1.0.20210117185518 5 | APP_VERSION=1.0 6 | TIMESTAMP=20210117185518 7 | DEPLOY_ENV=prd 8 | DOCKER_REPOSITORY= 9 | 10 | # 2. commit will push docker image to repository 11 | function commit() { 12 | local IMAGE=$1 13 | echo "docker push image : $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP" 14 | docker push $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP 15 | } 16 | 17 | # 3. build_api is the main function to build Dockerfile 18 | function build_api() { 19 | local IMAGE=automation-technology 20 | 21 | # If found go in default path, it will use go from default path 22 | GO=/usr/local/go/bin/go 23 | if [ -f "$GO" ]; then 24 | /usr/local/go/bin/go mod init automationworkshop/main 25 | /usr/local/go/bin/go get 26 | /usr/local/go/bin/go mod vendor 27 | else 28 | go mod init automationworkshop/main 29 | go get 30 | go mod vendor 31 | fi 32 | 33 | # Build the Dockerfile 34 | docker build -f Dockerfile -t $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP . 35 | commit $IMAGE 36 | } 37 | 38 | # 4. Validate APP_VERSION must not empty 39 | if [ "$APP_VERSION" = "" ]; then 40 | echo -e "APP_VERSION cannot be blank" 41 | exit 1 42 | fi 43 | 44 | # 5. Validate TIMESTAMP must not empty 45 | if [ "$TIMESTAMP" = "" ]; then 46 | echo -e "TIMESTAMP cannot be blank" 47 | exit 1 48 | fi 49 | 50 | # 6. Validate DEPLOY_ENV must not empty 51 | if [ "$DEPLOY_ENV" == "" ]; then 52 | echo -e "DEPLOY_ENV cannot be blank" 53 | exit 1 54 | fi 55 | 56 | # 7. Validate DOCKER_REPOSITORY must not empty 57 | if [ "$DOCKER_REPOSITORY" == "" ]; then 58 | echo -e "DOCKER_REPOSITORY cannot be blank" 59 | exit 1 60 | fi 61 | 62 | # 8. Run main build process 63 | build_api -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # exec env file if exists, .env might be in /.env or /config/.env 4 | FILE=/.env ; [ -f $FILE ] && . $FILE 5 | FILE=/config/.env ; [ -f $FILE ] && . $FILE 6 | 7 | # start main program 8 | exec /main -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/k8s/00-namespace.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: deploy-service 5 | labels: 6 | name: deploy-service 7 | module: Namespace -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/k8s/01-register-api.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: register-api 5 | namespace: deploy-service 6 | labels: 7 | name: register-api 8 | spec: 9 | replicas: 2 10 | strategy: 11 | type: RollingUpdate 12 | rollingUpdate: 13 | maxUnavailable: 1 14 | maxSurge: 1 15 | selector: 16 | matchLabels: 17 | name: register-api 18 | template: 19 | metadata: 20 | labels: 21 | name: register-api 22 | spec: 23 | containers: 24 | - name: register-api 25 | image: 3dsinteractive/automation-technology:prd-1.0.20210117185518 26 | imagePullPolicy: Always 27 | ports: 28 | - name: api8080 29 | containerPort: 8080 30 | resources: 31 | requests: 32 | memory: 500Mi 33 | cpu: 200m 34 | limits: 35 | memory: 1Gi 36 | cpu: 500m 37 | --- 38 | apiVersion: v1 39 | kind: Service 40 | metadata: 41 | name: register-api 42 | namespace: deploy-service 43 | labels: 44 | name: register-api 45 | spec: 46 | selector: 47 | name: register-api 48 | ports: 49 | - name: "api8080" 50 | port: 8080 51 | targetPort: 8080 52 | protocol: TCP 53 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/k8s/02-ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: ingress 5 | namespace: deploy-service 6 | labels: 7 | name: ingress 8 | spec: 9 | rules: 10 | - host: kubernetes.docker.internal 11 | http: 12 | paths: 13 | - path: / 14 | backend: 15 | serviceName: register-api 16 | servicePort: 8080 -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/main.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | ms := NewMicroservice() 10 | 11 | startHTTP(ms) 12 | 13 | ms.Start() 14 | } 15 | 16 | func startHTTP(ms *Microservice) { 17 | ms.POST("/citizen", func(ctx IContext) error { 18 | ctx.Log("POST: /citizen") 19 | status := map[string]interface{}{ 20 | "status": "success", 21 | } 22 | ctx.Response(http.StatusOK, status) 23 | return nil 24 | }) 25 | 26 | ms.GET("/citizen/:id", func(ctx IContext) error { 27 | id := ctx.Param("id") 28 | page := ctx.QueryParam("page") 29 | ctx.Log("GET: /citizen/" + id) 30 | citizen := map[string]interface{}{ 31 | "id": id, 32 | "page": page, 33 | } 34 | ctx.Response(http.StatusOK, citizen) 35 | return nil 36 | }) 37 | 38 | ms.PUT("/citizen/:id", func(ctx IContext) error { 39 | id := ctx.Param("id") 40 | ctx.Log("PUT: /citizen/" + id) 41 | citizen := map[string]interface{}{ 42 | "id": id, 43 | } 44 | ctx.Response(http.StatusOK, citizen) 45 | return nil 46 | }) 47 | 48 | ms.DELETE("/citizen/:id", func(ctx IContext) error { 49 | id := ctx.Param("id") 50 | ctx.Log("DELETE: /citizen/" + id) 51 | status := map[string]interface{}{ 52 | "status": "success", 53 | } 54 | ctx.Response(http.StatusOK, status) 55 | return nil 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.1-deploy-services/util.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "math/rand" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var specialCharsRegex = regexp.MustCompile("[^a-zA-Z0-9]+") 13 | 14 | func randString() string { 15 | i := rand.Int() 16 | return fmt.Sprintf("%d", i) 17 | } 18 | 19 | func escapeName(tokens ...string) string { 20 | // Any name rules 21 | // - Lowercase only (for consistency) 22 | // - . (dot), _ (underscore), - (minus) can be used 23 | // - Max length = 250 24 | var b bytes.Buffer 25 | 26 | // Name result must be token1-token2-token3-token4 without special characters 27 | for i, token := range tokens { 28 | if len(token) == 0 { 29 | continue 30 | } 31 | 32 | token = strings.ToLower(token) 33 | 34 | cleanToken := specialCharsRegex.ReplaceAllString(token, "-") 35 | if i != 0 { 36 | b.WriteString("-") 37 | } 38 | b.WriteString(cleanToken) 39 | } 40 | 41 | name := b.String() 42 | // - Cannot start with -, _, + 43 | for true { 44 | if len(name) == 0 || name[0] != '-' { 45 | break 46 | } 47 | name = name[1:] 48 | } 49 | 50 | // - Cannot be longer than 250 characters (max len) 51 | if len(name) > 250 { 52 | name = name[0:250] 53 | } 54 | 55 | return name 56 | } 57 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 3dsinteractive/golang:1.14-alpine3.9-librdfkafka1.4.0 2 | 3 | # 1. Add all go files and build 4 | WORKDIR /go/src/bitbucket.org/automationworkshop/main 5 | ADD . /go/src/bitbucket.org/automationworkshop/main 6 | RUN go build -mod vendor -i -tags "musl static_all" . 7 | 8 | # ================ 9 | FROM 3dsinteractive/alpine:3.9 10 | 11 | # 2. Use multi stage docker file, and copy executable file from previous stage 12 | COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 13 | COPY --from=0 /go/src/bitbucket.org/automationworkshop/main/main /main 14 | 15 | # 3. Add entrypoint.sh (The file that will use as entrypoint when run go program) 16 | ADD ./entrypoint.sh /entrypoint.sh 17 | 18 | # 4. Create user 1001 (It is my practice to always use user with id 1001) 19 | RUN adduser -u 1001 -D -s /bin/sh -G ping 1001 20 | RUN chown 1001:1001 /entrypoint.sh 21 | RUN chown 1001:1001 /main 22 | 23 | # 5. Make entrypoint.sh and main executable 24 | RUN chmod +x /entrypoint.sh 25 | RUN chmod +x /main 26 | 27 | # 6. Set default user 28 | USER 1001 29 | 30 | # 7. Expose port 8080 31 | EXPOSE 8080 32 | 33 | # 8. Start program by run entrypoint.sh 34 | ENTRYPOINT ["/entrypoint.sh"] 35 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/README: -------------------------------------------------------------------------------- 1 | Healthcheck Readiness 2 | 3 | 0. Look into the slides about service healthcheck 4 | 5 | 1. Open vscode at chapter03-deploy-healthchecks/3.2-healthcheck-readiness 6 | 7 | 2. Register for https://hub.docker.com 8 | 9 | 3. Create public repository call [your-docker-repository-name]/automation-technology 10 | 11 | 4. Make sure docker is running (Check the docker icon in task bar) 12 | 13 | 5. Make sure you are logged in with your docker account 14 | $ docker login 15 | [username] 16 | [password] 17 | 18 | 6. Update file deploy.sh change 19 | DOCKER_REPOSITORY= 20 | to 21 | DOCKER_REPOSITORY=[your-docker-repository-name] 22 | 23 | 7. Run command (deploy.sh will be the file used to build your project, especially when it integrate with ci/cd) 24 | $ ./deploy.sh 25 | This will build the Dockerfile and push image to [your-docker-repository-name]/automation-technology 26 | 27 | 8. Run command 28 | $ cd k8s 29 | 30 | 9. Run command to start databases 31 | $ kubectl apply -f 00-databases/. 32 | 33 | ** Wait until redis is OK 34 | 35 | 10. Run command 36 | $ kubectl get po -n healthcheck 37 | NAME READY STATUS RESTARTS AGE 38 | redis-577d58dd6c-c6595 1/1 Running 0 3m55s 39 | 40 | 11. Run command to start application 41 | $ kubectl apply -f 01-application/. 42 | 43 | 12. Run command to make sure all services is OK 44 | $ kubectl get po -n healthcheck 45 | register-api-758f494ccd-7xqmx 1/1 Running 0 109s 46 | register-api-758f494ccd-gbtdg 1/1 Running 0 109s 47 | 48 | 13. Run command 49 | $ curl -X POST "http://kubernetes.docker.internal/citizen" 50 | {"status":"success"} 51 | 52 | 14. Run command 53 | $ watch "kubectl get po -n healthcheck" 54 | 55 | 15. Open other terminal and Run command to kill redis 56 | $ kubectl delete -f 01-redis.yml 57 | 58 | 16. Wait to see service healthcheck fail 59 | ** Notice RESTARTS will increase 60 | 61 | 17. Run command to get redis back 62 | $ kubectl apply -f 01-redis.yml 63 | 64 | 18. Wait to see service come back 65 | 66 | 19. Explain healthcheck endpoint in sourcecode 67 | 68 | 20. Run command 69 | $ kubectl delete ns healthcheck -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | // IContext is the context for service 5 | type IContext interface { 6 | Log(message string) 7 | Param(name string) string 8 | QueryParam(name string) string 9 | Response(responseCode int, responseData interface{}) 10 | ReadInput() string 11 | ReadInputs() []string 12 | 13 | // Dependency 14 | Cacher(server string) ICacher 15 | Producer(servers string) IProducer 16 | MQ(servers string) IMQ 17 | } 18 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/context_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // ConsumerContext implement IContext it is context for Consumer 11 | type ConsumerContext struct { 12 | ms *Microservice 13 | message string 14 | } 15 | 16 | // NewConsumerContext is the constructor function for ConsumerContext 17 | func NewConsumerContext(ms *Microservice, message string) *ConsumerContext { 18 | return &ConsumerContext{ 19 | ms: ms, 20 | message: message, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *ConsumerContext) Log(message string) { 26 | _, fn, line, _ := runtime.Caller(1) 27 | fns := strings.Split(fn, "/") 28 | fmt.Println("Consumer:", fns[len(fns)-1], line, message) 29 | } 30 | 31 | // Param return parameter by name (empty in case of Consumer) 32 | func (ctx *ConsumerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // QueryParam return empty in consumer 37 | func (ctx *ConsumerContext) QueryParam(name string) string { 38 | return "" 39 | } 40 | 41 | // ReadInput return message 42 | func (ctx *ConsumerContext) ReadInput() string { 43 | return ctx.message 44 | } 45 | 46 | // ReadInputs return nil in case Consumer 47 | func (ctx *ConsumerContext) ReadInputs() []string { 48 | return nil 49 | } 50 | 51 | // Response return response to client 52 | func (ctx *ConsumerContext) Response(responseCode int, responseData interface{}) { 53 | return 54 | } 55 | 56 | // Cacher return cacher 57 | func (ctx *ConsumerContext) Cacher(server string) ICacher { 58 | return ctx.ms.getCacher(server) 59 | } 60 | 61 | // Producer return producer 62 | func (ctx *ConsumerContext) Producer(servers string) IProducer { 63 | return ctx.ms.getProducer(servers) 64 | } 65 | 66 | // MQ return MQ 67 | func (ctx *ConsumerContext) MQ(servers string) IMQ { 68 | return NewMQ(servers, ctx.ms) 69 | } 70 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/context_consumer_batch.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // BatchConsumerContext implement IContext it is context for Consumer 11 | type BatchConsumerContext struct { 12 | ms *Microservice 13 | messages []string 14 | } 15 | 16 | // NewBatchConsumerContext is the constructor function for BatchConsumerContext 17 | func NewBatchConsumerContext(ms *Microservice, messages []string) *BatchConsumerContext { 18 | return &BatchConsumerContext{ 19 | ms: ms, 20 | messages: messages, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *BatchConsumerContext) Log(message string) { 26 | _, fn, line, _ := runtime.Caller(1) 27 | fns := strings.Split(fn, "/") 28 | fmt.Println("Batch Consumer:", fns[len(fns)-1], line, message) 29 | } 30 | 31 | // Param return parameter by name (empty in case of Consumer) 32 | func (ctx *BatchConsumerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // QueryParam return empty in consumer batch 37 | func (ctx *BatchConsumerContext) QueryParam(name string) string { 38 | return "" 39 | } 40 | 41 | // ReadInput return message (return empty in batch consumer) 42 | func (ctx *BatchConsumerContext) ReadInput() string { 43 | return "" 44 | } 45 | 46 | // ReadInputs return messages in batch 47 | func (ctx *BatchConsumerContext) ReadInputs() []string { 48 | return ctx.messages 49 | } 50 | 51 | // Response return response to client 52 | func (ctx *BatchConsumerContext) Response(responseCode int, responseData interface{}) { 53 | return 54 | } 55 | 56 | // Cacher return cacher 57 | func (ctx *BatchConsumerContext) Cacher(server string) ICacher { 58 | return ctx.ms.getCacher(server) 59 | } 60 | 61 | // Producer return producer 62 | func (ctx *BatchConsumerContext) Producer(servers string) IProducer { 63 | return ctx.ms.getProducer(servers) 64 | } 65 | 66 | // MQ return MQ 67 | func (ctx *BatchConsumerContext) MQ(servers string) IMQ { 68 | return NewMQ(servers, ctx.ms) 69 | } 70 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/context_http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "runtime" 7 | "strings" 8 | 9 | "github.com/labstack/echo" 10 | ) 11 | 12 | // HTTPContext implement IContext it is context for HTTP 13 | type HTTPContext struct { 14 | ms *Microservice 15 | c echo.Context 16 | } 17 | 18 | // NewHTTPContext is the constructor function for HTTPContext 19 | func NewHTTPContext(ms *Microservice, c echo.Context) *HTTPContext { 20 | return &HTTPContext{ 21 | ms: ms, 22 | c: c, 23 | } 24 | } 25 | 26 | // Log will log a message 27 | func (ctx *HTTPContext) Log(message string) { 28 | _, fn, line, _ := runtime.Caller(1) 29 | fns := strings.Split(fn, "/") 30 | fmt.Println("HTTP:", fns[len(fns)-1], line, message) 31 | } 32 | 33 | // Param return parameter by name 34 | func (ctx *HTTPContext) Param(name string) string { 35 | return ctx.c.Param(name) 36 | } 37 | 38 | // QueryParam return query param 39 | func (ctx *HTTPContext) QueryParam(name string) string { 40 | return ctx.c.QueryParam(name) 41 | } 42 | 43 | // ReadInput read the request body and return it as string 44 | func (ctx *HTTPContext) ReadInput() string { 45 | body, err := ioutil.ReadAll(ctx.c.Request().Body) 46 | if err != nil { 47 | return "" 48 | } 49 | return string(body) 50 | } 51 | 52 | // ReadInputs return nil in HTTP Context 53 | func (ctx *HTTPContext) ReadInputs() []string { 54 | return nil 55 | } 56 | 57 | // Response return response to client 58 | func (ctx *HTTPContext) Response(responseCode int, responseData interface{}) { 59 | ctx.c.JSON(responseCode, responseData) 60 | } 61 | 62 | // Cacher return cacher 63 | func (ctx *HTTPContext) Cacher(server string) ICacher { 64 | return ctx.ms.getCacher(server) 65 | } 66 | 67 | // Producer return producer 68 | func (ctx *HTTPContext) Producer(servers string) IProducer { 69 | return ctx.ms.getProducer(servers) 70 | } 71 | 72 | // MQ return MQ 73 | func (ctx *HTTPContext) MQ(servers string) IMQ { 74 | return NewMQ(servers, ctx.ms) 75 | } 76 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/context_scheduler.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // SchedulerContext implement IContext it is context for Consumer 11 | type SchedulerContext struct { 12 | ms *Microservice 13 | } 14 | 15 | // NewSchedulerContext is the constructor function for SchedulerContext 16 | func NewSchedulerContext(ms *Microservice) *SchedulerContext { 17 | return &SchedulerContext{ 18 | ms: ms, 19 | } 20 | } 21 | 22 | // Log will log a message 23 | func (ctx *SchedulerContext) Log(message string) { 24 | _, fn, line, _ := runtime.Caller(1) 25 | fns := strings.Split(fn, "/") 26 | fmt.Println("Scheduler:", fns[len(fns)-1], line, message) 27 | } 28 | 29 | // Param return parameter by name (empty in scheduler) 30 | func (ctx *SchedulerContext) Param(name string) string { 31 | return "" 32 | } 33 | 34 | // QueryParam return empty in scheduler 35 | func (ctx *SchedulerContext) QueryParam(name string) string { 36 | return "" 37 | } 38 | 39 | // ReadInput return message (return empty in scheduler) 40 | func (ctx *SchedulerContext) ReadInput() string { 41 | return "" 42 | } 43 | 44 | // ReadInputs return messages in batch (return nil in scheduler) 45 | func (ctx *SchedulerContext) ReadInputs() []string { 46 | return nil 47 | } 48 | 49 | // Response return response to client 50 | func (ctx *SchedulerContext) Response(responseCode int, responseData interface{}) { 51 | return 52 | } 53 | 54 | // Cacher return cacher 55 | func (ctx *SchedulerContext) Cacher(server string) ICacher { 56 | return ctx.ms.getCacher(server) 57 | } 58 | 59 | // Producer return producer 60 | func (ctx *SchedulerContext) Producer(servers string) IProducer { 61 | return ctx.ms.getProducer(servers) 62 | } 63 | 64 | // MQ return MQ 65 | func (ctx *SchedulerContext) MQ(servers string) IMQ { 66 | return NewMQ(servers, ctx.ms) 67 | } 68 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1. Format of docker image is $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP 4 | # for example 3dsinteractive/automation-technology:prd-1.0.20210117185519 5 | APP_VERSION=1.0 6 | TIMESTAMP=20210117185519 7 | DEPLOY_ENV=prd 8 | DOCKER_REPOSITORY= 9 | 10 | # 2. commit will push docker image to repository 11 | function commit() { 12 | local IMAGE=$1 13 | echo "docker push image : $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP" 14 | docker push $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP 15 | } 16 | 17 | # 3. build_api is the main function to build Dockerfile 18 | function build_api() { 19 | local IMAGE=automation-technology 20 | 21 | # If found go in default path, it will use go from default path 22 | GO=/usr/local/go/bin/go 23 | if [ -f "$GO" ]; then 24 | /usr/local/go/bin/go mod init automationworkshop/main 25 | /usr/local/go/bin/go get 26 | /usr/local/go/bin/go mod vendor 27 | else 28 | go mod init automationworkshop/main 29 | go get 30 | go mod vendor 31 | fi 32 | 33 | # Build the Dockerfile 34 | docker build -f Dockerfile -t $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP . 35 | commit $IMAGE 36 | } 37 | 38 | # 4. Validate APP_VERSION must not empty 39 | if [ "$APP_VERSION" = "" ]; then 40 | echo -e "APP_VERSION cannot be blank" 41 | exit 1 42 | fi 43 | 44 | # 5. Validate TIMESTAMP must not empty 45 | if [ "$TIMESTAMP" = "" ]; then 46 | echo -e "TIMESTAMP cannot be blank" 47 | exit 1 48 | fi 49 | 50 | # 6. Validate DEPLOY_ENV must not empty 51 | if [ "$DEPLOY_ENV" == "" ]; then 52 | echo -e "DEPLOY_ENV cannot be blank" 53 | exit 1 54 | fi 55 | 56 | # 7. Validate DOCKER_REPOSITORY must not empty 57 | if [ "$DOCKER_REPOSITORY" == "" ]; then 58 | echo -e "DOCKER_REPOSITORY cannot be blank" 59 | exit 1 60 | fi 61 | 62 | # 8. Run main build process 63 | build_api -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # exec env file if exists, .env might be in /.env or /config/.env 4 | FILE=/.env ; [ -f $FILE ] && . $FILE 5 | FILE=/config/.env ; [ -f $FILE ] && . $FILE 6 | 7 | # start main program 8 | exec /main -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/k8s/00-databases/00-namespace.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: healthcheck 5 | labels: 6 | name: healthcheck 7 | module: Namespace -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/k8s/00-databases/01-redis.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: redis 5 | namespace: healthcheck 6 | labels: 7 | name: redis 8 | spec: 9 | replicas: 1 10 | strategy: 11 | type: Recreate 12 | selector: 13 | matchLabels: 14 | name: redis 15 | template: 16 | metadata: 17 | labels: 18 | name: redis 19 | spec: 20 | containers: 21 | - name: redis 22 | image: 3dsinteractive/redis:5.0 23 | imagePullPolicy: Always 24 | ports: 25 | - name: redis6379 26 | containerPort: 6379 27 | env: 28 | - name: ALLOW_EMPTY_PASSWORD 29 | value: "yes" 30 | resources: 31 | requests: 32 | memory: 500Mi 33 | cpu: 200m 34 | limits: 35 | memory: 1Gi 36 | cpu: 500m 37 | livenessProbe: 38 | tcpSocket: 39 | port: 6379 40 | initialDelaySeconds: 30 41 | timeoutSeconds: 1 42 | periodSeconds: 300 43 | readinessProbe: 44 | tcpSocket: 45 | port: 6379 46 | initialDelaySeconds: 30 47 | timeoutSeconds: 1 48 | periodSeconds: 30 49 | failureThreshold: 5 50 | --- 51 | apiVersion: v1 52 | kind: Service 53 | metadata: 54 | name: redis 55 | namespace: healthcheck 56 | labels: 57 | name: redis 58 | spec: 59 | selector: 60 | name: redis 61 | ports: 62 | - name: redis6379 63 | port: 6379 64 | targetPort: 6379 65 | protocol: TCP 66 | clusterIP: None -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/k8s/01-application/02-register-api.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: register-api 5 | namespace: healthcheck 6 | labels: 7 | name: register-api 8 | spec: 9 | replicas: 2 10 | strategy: 11 | type: RollingUpdate 12 | rollingUpdate: 13 | maxUnavailable: 1 14 | maxSurge: 1 15 | selector: 16 | matchLabels: 17 | name: register-api 18 | template: 19 | metadata: 20 | labels: 21 | name: register-api 22 | spec: 23 | containers: 24 | - name: register-api 25 | image: 3dsinteractive/automation-technology:prd-1.0.20210117185519 26 | imagePullPolicy: Always 27 | readinessProbe: 28 | httpGet: 29 | path: /healthz 30 | port: 8080 31 | initialDelaySeconds: 10 32 | periodSeconds: 10 33 | timeoutSeconds: 3 34 | failureThreshold: 1 35 | livenessProbe: 36 | httpGet: 37 | path: /healthz 38 | port: 8080 39 | initialDelaySeconds: 30 40 | periodSeconds: 10 41 | timeoutSeconds: 5 42 | failureThreshold: 1 43 | ports: 44 | - name: api8080 45 | containerPort: 8080 46 | resources: 47 | requests: 48 | memory: 500Mi 49 | cpu: 200m 50 | limits: 51 | memory: 1Gi 52 | cpu: 500m 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | name: register-api 58 | namespace: healthcheck 59 | labels: 60 | name: register-api 61 | spec: 62 | selector: 63 | name: register-api 64 | ports: 65 | - name: "api8080" 66 | port: 8080 67 | targetPort: 8080 68 | protocol: TCP 69 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/k8s/01-application/03-ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: ingress 5 | namespace: healthcheck 6 | labels: 7 | name: ingress 8 | spec: 9 | rules: 10 | - host: kubernetes.docker.internal 11 | http: 12 | paths: 13 | - path: / 14 | backend: 15 | serviceName: register-api 16 | servicePort: 8080 -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/main.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | ms := NewMicroservice() 11 | 12 | cacheServer := "redis:6379" 13 | cacher := ms.getCacher(cacheServer) 14 | cacher.Set("key1", "value1", time.Duration(-1)) 15 | 16 | // 1. Healthcheck endpoint will register to /healthz 17 | ms.RegisterLivenessProbeEndpoint("/healthz") 18 | 19 | // 2. Start application services 20 | startHTTP(ms) 21 | 22 | ms.Start() 23 | } 24 | 25 | func startHTTP(ms *Microservice) { 26 | ms.POST("/citizen", func(ctx IContext) error { 27 | ctx.Log("POST: /citizen") 28 | status := map[string]interface{}{ 29 | "status": "success", 30 | } 31 | ctx.Response(http.StatusOK, status) 32 | return nil 33 | }) 34 | 35 | ms.GET("/citizen/:id", func(ctx IContext) error { 36 | id := ctx.Param("id") 37 | page := ctx.QueryParam("page") 38 | ctx.Log("GET: /citizen/" + id) 39 | citizen := map[string]interface{}{ 40 | "id": id, 41 | "page": page, 42 | } 43 | ctx.Response(http.StatusOK, citizen) 44 | return nil 45 | }) 46 | 47 | ms.PUT("/citizen/:id", func(ctx IContext) error { 48 | id := ctx.Param("id") 49 | ctx.Log("PUT: /citizen/" + id) 50 | citizen := map[string]interface{}{ 51 | "id": id, 52 | } 53 | ctx.Response(http.StatusOK, citizen) 54 | return nil 55 | }) 56 | 57 | ms.DELETE("/citizen/:id", func(ctx IContext) error { 58 | id := ctx.Param("id") 59 | ctx.Log("DELETE: /citizen/" + id) 60 | status := map[string]interface{}{ 61 | "status": "success", 62 | } 63 | ctx.Response(http.StatusOK, status) 64 | return nil 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/microservice_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/confluentinc/confluent-kafka-go/kafka" 8 | ) 9 | 10 | func (ms *Microservice) consumeSingle(servers string, topic string, groupID string, readTimeout time.Duration, h ServiceHandleFunc) { 11 | c, err := ms.newKafkaConsumer(servers, groupID) 12 | if err != nil { 13 | return 14 | } 15 | 16 | defer c.Close() 17 | 18 | c.Subscribe(topic, nil) 19 | 20 | for { 21 | if readTimeout <= 0 { 22 | // readtimeout -1 indicates no timeout 23 | readTimeout = -1 24 | } 25 | 26 | msg, err := c.ReadMessage(readTimeout) 27 | if err != nil { 28 | kafkaErr, ok := err.(kafka.Error) 29 | if ok { 30 | if kafkaErr.Code() == kafka.ErrTimedOut { 31 | if readTimeout == -1 { 32 | // No timeout just continue to read message again 33 | continue 34 | } 35 | } 36 | } 37 | ms.Log("Consumer", err.Error()) 38 | return 39 | } 40 | 41 | // Execute Handler 42 | h(NewConsumerContext(ms, string(msg.Value))) 43 | } 44 | } 45 | 46 | // Consume register service endpoint for Consumer service 47 | func (ms *Microservice) Consume(servers string, topic string, groupID string, readTimeout time.Duration, h ServiceHandleFunc) error { 48 | go ms.consumeSingle(servers, topic, groupID, readTimeout, h) 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/microservice_http.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "context" 6 | "time" 7 | 8 | "github.com/labstack/echo" 9 | ) 10 | 11 | // GET register service endpoint for HTTP GET 12 | func (ms *Microservice) GET(path string, h ServiceHandleFunc) { 13 | ms.echo.GET(path, func(c echo.Context) error { 14 | return h(NewHTTPContext(ms, c)) 15 | }) 16 | } 17 | 18 | // POST register service endpoint for HTTP POST 19 | func (ms *Microservice) POST(path string, h ServiceHandleFunc) { 20 | ms.echo.POST(path, func(c echo.Context) error { 21 | return h(NewHTTPContext(ms, c)) 22 | }) 23 | } 24 | 25 | // PUT register service endpoint for HTTP PUT 26 | func (ms *Microservice) PUT(path string, h ServiceHandleFunc) { 27 | ms.echo.PUT(path, func(c echo.Context) error { 28 | return h(NewHTTPContext(ms, c)) 29 | }) 30 | } 31 | 32 | // PATCH register service endpoint for HTTP PATCH 33 | func (ms *Microservice) PATCH(path string, h ServiceHandleFunc) { 34 | ms.echo.PATCH(path, func(c echo.Context) error { 35 | return h(NewHTTPContext(ms, c)) 36 | }) 37 | } 38 | 39 | // DELETE register service endpoint for HTTP DELETE 40 | func (ms *Microservice) DELETE(path string, h ServiceHandleFunc) { 41 | ms.echo.DELETE(path, func(c echo.Context) error { 42 | return h(NewHTTPContext(ms, c)) 43 | }) 44 | } 45 | 46 | // startHTTP will start HTTP service, this function will block thread 47 | func (ms *Microservice) startHTTP(exitChannel chan bool) error { 48 | // Caller can exit by sending value to exitChannel 49 | go func() { 50 | <-exitChannel 51 | ms.stopHTTP() 52 | }() 53 | return ms.echo.Start(":8080") 54 | } 55 | 56 | func (ms *Microservice) stopHTTP() { 57 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 58 | defer cancel() 59 | ms.echo.Shutdown(ctx) 60 | } 61 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/microservice_liveness.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "net/http" 6 | 7 | "github.com/labstack/echo" 8 | ) 9 | 10 | func (ms *Microservice) isCacherAlive() bool { 11 | if ms.cacher == nil { 12 | return true 13 | } 14 | 15 | ms.Log("MS", "Perform healthcheck on Cacher") 16 | err := ms.cacher.Healthcheck() 17 | if err != nil { 18 | return false 19 | } 20 | return true 21 | } 22 | 23 | func (ms *Microservice) isAlive() (bool, string) { 24 | // 1. We will check dependency if it is OK, in this case the dependency is Redis 25 | isAlive := ms.isCacherAlive() 26 | if !isAlive { 27 | return false, "Cacher healthcheck failed" 28 | } 29 | 30 | // 2. If we have other dependency, we will add them here such as 31 | // isAlive = ms.isMariaDBAlive() 32 | // if !isAlive { 33 | // return false, "MariaDB healthcheck failed" 34 | // } 35 | 36 | return true, "" 37 | } 38 | 39 | func (ms *Microservice) responseProbeOK(resp *echo.Response) { 40 | resp.WriteHeader(http.StatusOK) 41 | resp.Write([]byte("ok")) 42 | } 43 | 44 | func (ms *Microservice) responseProbeFailed(resp *echo.Response, reason string) { 45 | errMsg := "Healthcheck failed because of " + reason 46 | resp.WriteHeader(http.StatusInternalServerError) 47 | resp.Write([]byte(errMsg)) 48 | } 49 | 50 | // RegisterLivenessProbeEndpoint register endpoint for liveness probe 51 | func (ms *Microservice) RegisterLivenessProbeEndpoint(path string) { 52 | ms.echo.GET(path, func(c echo.Context) error { 53 | // If Microservice isAlive return !ok, it is because some dependency is failed 54 | ok, reason := ms.isAlive() 55 | if !ok { 56 | // If !ok we will response status 500 error 57 | ms.responseProbeFailed(c.Response(), reason) 58 | return nil 59 | } 60 | // If ok we will response 200 OK 61 | ms.responseProbeOK(c.Response()) 62 | return nil 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/microservice_scheduler.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Schedule will run handler at timer period 10 | func (ms *Microservice) Schedule(timer time.Duration, h ServiceHandleFunc) chan bool /*exit channel*/ { 11 | 12 | // exitChan must be call exitChan <- true from caller to exit scheduler 13 | exitChan := make(chan bool, 1) 14 | go func() { 15 | t := time.NewTicker(timer) 16 | done := make(chan bool, 1) 17 | isExit := false 18 | isExitMutex := sync.Mutex{} 19 | 20 | go func() { 21 | <-exitChan 22 | isExitMutex.Lock() 23 | isExit = true 24 | isExitMutex.Unlock() 25 | // Stop Tick() and send done message to exit for loop below 26 | // Ref: From the documentation http://golang.org/pkg/time/#Ticker.Stop 27 | // Stop turns off a ticker. After Stop, no more ticks will be sent. 28 | // Stop does not close the channel, to prevent a read from the channel succeeding incorrectly. 29 | t.Stop() 30 | done <- true 31 | }() 32 | 33 | for { 34 | select { 35 | case execTime := <-t.C: 36 | isExitMutex.Lock() 37 | if isExit { 38 | isExitMutex.Unlock() 39 | // Done in the next round 40 | continue 41 | } 42 | isExitMutex.Unlock() 43 | 44 | now := time.Now() 45 | // The schedule that older than 10s, will be skip, because t.C is buffer at size 1 46 | diff := now.Sub(execTime).Seconds() 47 | if diff > 10 { 48 | continue 49 | } 50 | h(NewSchedulerContext(ms)) 51 | case <-done: 52 | return 53 | } 54 | } 55 | }() 56 | 57 | return exitChan 58 | } 59 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.2-healthcheck-readiness/util.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "math/rand" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var specialCharsRegex = regexp.MustCompile("[^a-zA-Z0-9]+") 13 | 14 | func randString() string { 15 | i := rand.Int() 16 | return fmt.Sprintf("%d", i) 17 | } 18 | 19 | func escapeName(tokens ...string) string { 20 | // Any name rules 21 | // - Lowercase only (for consistency) 22 | // - . (dot), _ (underscore), - (minus) can be used 23 | // - Max length = 250 24 | var b bytes.Buffer 25 | 26 | // Name result must be token1-token2-token3-token4 without special characters 27 | for i, token := range tokens { 28 | if len(token) == 0 { 29 | continue 30 | } 31 | 32 | token = strings.ToLower(token) 33 | 34 | cleanToken := specialCharsRegex.ReplaceAllString(token, "-") 35 | if i != 0 { 36 | b.WriteString("-") 37 | } 38 | b.WriteString(cleanToken) 39 | } 40 | 41 | name := b.String() 42 | // - Cannot start with -, _, + 43 | for true { 44 | if len(name) == 0 || name[0] != '-' { 45 | break 46 | } 47 | name = name[1:] 48 | } 49 | 50 | // - Cannot be longer than 250 characters (max len) 51 | if len(name) > 250 { 52 | name = name[0:250] 53 | } 54 | 55 | return name 56 | } 57 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 3dsinteractive/golang:1.14-alpine3.9-librdfkafka1.4.0 2 | 3 | WORKDIR /go/src/bitbucket.org/automationworkshop/main 4 | ADD . /go/src/bitbucket.org/automationworkshop/main 5 | RUN go build -mod vendor -i -tags "musl static_all" . 6 | 7 | # ================ 8 | FROM 3dsinteractive/alpine:3.9 9 | 10 | COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 11 | COPY --from=0 /go/src/bitbucket.org/automationworkshop/main/main /main 12 | 13 | ADD ./entrypoint.sh /entrypoint.sh 14 | 15 | RUN adduser -u 1001 -D -s /bin/sh -G ping 1001 16 | RUN chown 1001:1001 /entrypoint.sh 17 | RUN chown 1001:1001 /main 18 | 19 | RUN chmod +x /entrypoint.sh 20 | RUN chmod +x /main 21 | 22 | USER 1001 23 | 24 | EXPOSE 8080 25 | 26 | ENTRYPOINT ["/entrypoint.sh"] 27 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/config.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import "os" 5 | 6 | // IConfig is interface for application config 7 | type IConfig interface { 8 | ServiceID() string 9 | CacheServer() string 10 | MQServers() string 11 | } 12 | 13 | // Config implement IConfig 14 | type Config struct{} 15 | 16 | // NewConfig return new config instance 17 | func NewConfig() *Config { 18 | return &Config{} 19 | } 20 | 21 | // ServiceID return ID of service 22 | func (cfg *Config) ServiceID() string { 23 | return os.Getenv("SERVICE_ID") 24 | } 25 | 26 | // CacheServer return redis server 27 | func (cfg *Config) CacheServer() string { 28 | return os.Getenv("CACHE_SERVER") 29 | } 30 | 31 | // MQServers return Kafka servers 32 | func (cfg *Config) MQServers() string { 33 | return os.Getenv("MQ_SERVERS") 34 | } 35 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | // IContext is the context for service 5 | type IContext interface { 6 | Log(message string) 7 | Param(name string) string 8 | QueryParam(name string) string 9 | Response(responseCode int, responseData interface{}) 10 | ReadInput() string 11 | ReadInputs() []string 12 | 13 | // Dependency 14 | Cacher(server string) ICacher 15 | Producer(servers string) IProducer 16 | MQ(servers string) IMQ 17 | } 18 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/context_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // ConsumerContext implement IContext it is context for Consumer 11 | type ConsumerContext struct { 12 | ms *Microservice 13 | message string 14 | } 15 | 16 | // NewConsumerContext is the constructor function for ConsumerContext 17 | func NewConsumerContext(ms *Microservice, message string) *ConsumerContext { 18 | return &ConsumerContext{ 19 | ms: ms, 20 | message: message, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *ConsumerContext) Log(message string) { 26 | _, fn, line, _ := runtime.Caller(1) 27 | fns := strings.Split(fn, "/") 28 | fmt.Println("Consumer:", fns[len(fns)-1], line, message) 29 | } 30 | 31 | // Param return parameter by name (empty in case of Consumer) 32 | func (ctx *ConsumerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // QueryParam return empty in consumer 37 | func (ctx *ConsumerContext) QueryParam(name string) string { 38 | return "" 39 | } 40 | 41 | // ReadInput return message 42 | func (ctx *ConsumerContext) ReadInput() string { 43 | return ctx.message 44 | } 45 | 46 | // ReadInputs return nil in case Consumer 47 | func (ctx *ConsumerContext) ReadInputs() []string { 48 | return nil 49 | } 50 | 51 | // Response return response to client 52 | func (ctx *ConsumerContext) Response(responseCode int, responseData interface{}) { 53 | return 54 | } 55 | 56 | // Cacher return cacher 57 | func (ctx *ConsumerContext) Cacher(server string) ICacher { 58 | return ctx.ms.getCacher(server) 59 | } 60 | 61 | // Producer return producer 62 | func (ctx *ConsumerContext) Producer(servers string) IProducer { 63 | return ctx.ms.getProducer(servers) 64 | } 65 | 66 | // MQ return MQ 67 | func (ctx *ConsumerContext) MQ(servers string) IMQ { 68 | return NewMQ(servers, ctx.ms) 69 | } 70 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/context_consumer_batch.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // BatchConsumerContext implement IContext it is context for Consumer 11 | type BatchConsumerContext struct { 12 | ms *Microservice 13 | messages []string 14 | } 15 | 16 | // NewBatchConsumerContext is the constructor function for BatchConsumerContext 17 | func NewBatchConsumerContext(ms *Microservice, messages []string) *BatchConsumerContext { 18 | return &BatchConsumerContext{ 19 | ms: ms, 20 | messages: messages, 21 | } 22 | } 23 | 24 | // Log will log a message 25 | func (ctx *BatchConsumerContext) Log(message string) { 26 | _, fn, line, _ := runtime.Caller(1) 27 | fns := strings.Split(fn, "/") 28 | fmt.Println("Batch Consumer:", fns[len(fns)-1], line, message) 29 | } 30 | 31 | // Param return parameter by name (empty in case of Consumer) 32 | func (ctx *BatchConsumerContext) Param(name string) string { 33 | return "" 34 | } 35 | 36 | // QueryParam return empty in consumer batch 37 | func (ctx *BatchConsumerContext) QueryParam(name string) string { 38 | return "" 39 | } 40 | 41 | // ReadInput return message (return empty in batch consumer) 42 | func (ctx *BatchConsumerContext) ReadInput() string { 43 | return "" 44 | } 45 | 46 | // ReadInputs return messages in batch 47 | func (ctx *BatchConsumerContext) ReadInputs() []string { 48 | return ctx.messages 49 | } 50 | 51 | // Response return response to client 52 | func (ctx *BatchConsumerContext) Response(responseCode int, responseData interface{}) { 53 | return 54 | } 55 | 56 | // Cacher return cacher 57 | func (ctx *BatchConsumerContext) Cacher(server string) ICacher { 58 | return ctx.ms.getCacher(server) 59 | } 60 | 61 | // Producer return producer 62 | func (ctx *BatchConsumerContext) Producer(servers string) IProducer { 63 | return ctx.ms.getProducer(servers) 64 | } 65 | 66 | // MQ return MQ 67 | func (ctx *BatchConsumerContext) MQ(servers string) IMQ { 68 | return NewMQ(servers, ctx.ms) 69 | } 70 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/context_http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "runtime" 7 | "strings" 8 | 9 | "github.com/labstack/echo" 10 | ) 11 | 12 | // HTTPContext implement IContext it is context for HTTP 13 | type HTTPContext struct { 14 | ms *Microservice 15 | c echo.Context 16 | } 17 | 18 | // NewHTTPContext is the constructor function for HTTPContext 19 | func NewHTTPContext(ms *Microservice, c echo.Context) *HTTPContext { 20 | return &HTTPContext{ 21 | ms: ms, 22 | c: c, 23 | } 24 | } 25 | 26 | // Log will log a message 27 | func (ctx *HTTPContext) Log(message string) { 28 | _, fn, line, _ := runtime.Caller(1) 29 | fns := strings.Split(fn, "/") 30 | fmt.Println("HTTP:", fns[len(fns)-1], line, message) 31 | } 32 | 33 | // Param return parameter by name 34 | func (ctx *HTTPContext) Param(name string) string { 35 | return ctx.c.Param(name) 36 | } 37 | 38 | // QueryParam return query param 39 | func (ctx *HTTPContext) QueryParam(name string) string { 40 | return ctx.c.QueryParam(name) 41 | } 42 | 43 | // ReadInput read the request body and return it as string 44 | func (ctx *HTTPContext) ReadInput() string { 45 | body, err := ioutil.ReadAll(ctx.c.Request().Body) 46 | if err != nil { 47 | return "" 48 | } 49 | return string(body) 50 | } 51 | 52 | // ReadInputs return nil in HTTP Context 53 | func (ctx *HTTPContext) ReadInputs() []string { 54 | return nil 55 | } 56 | 57 | // Response return response to client 58 | func (ctx *HTTPContext) Response(responseCode int, responseData interface{}) { 59 | ctx.c.JSON(responseCode, responseData) 60 | } 61 | 62 | // Cacher return cacher 63 | func (ctx *HTTPContext) Cacher(server string) ICacher { 64 | return ctx.ms.getCacher(server) 65 | } 66 | 67 | // Producer return producer 68 | func (ctx *HTTPContext) Producer(servers string) IProducer { 69 | return ctx.ms.getProducer(servers) 70 | } 71 | 72 | // MQ return MQ 73 | func (ctx *HTTPContext) MQ(servers string) IMQ { 74 | return NewMQ(servers, ctx.ms) 75 | } 76 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/context_scheduler.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // SchedulerContext implement IContext it is context for Consumer 11 | type SchedulerContext struct { 12 | ms *Microservice 13 | } 14 | 15 | // NewSchedulerContext is the constructor function for SchedulerContext 16 | func NewSchedulerContext(ms *Microservice) *SchedulerContext { 17 | return &SchedulerContext{ 18 | ms: ms, 19 | } 20 | } 21 | 22 | // Log will log a message 23 | func (ctx *SchedulerContext) Log(message string) { 24 | _, fn, line, _ := runtime.Caller(1) 25 | fns := strings.Split(fn, "/") 26 | fmt.Println("Scheduler:", fns[len(fns)-1], line, message) 27 | } 28 | 29 | // Param return parameter by name (empty in scheduler) 30 | func (ctx *SchedulerContext) Param(name string) string { 31 | return "" 32 | } 33 | 34 | // QueryParam return empty in scheduler 35 | func (ctx *SchedulerContext) QueryParam(name string) string { 36 | return "" 37 | } 38 | 39 | // ReadInput return message (return empty in scheduler) 40 | func (ctx *SchedulerContext) ReadInput() string { 41 | return "" 42 | } 43 | 44 | // ReadInputs return messages in batch (return nil in scheduler) 45 | func (ctx *SchedulerContext) ReadInputs() []string { 46 | return nil 47 | } 48 | 49 | // Response return response to client 50 | func (ctx *SchedulerContext) Response(responseCode int, responseData interface{}) { 51 | return 52 | } 53 | 54 | // Cacher return cacher 55 | func (ctx *SchedulerContext) Cacher(server string) ICacher { 56 | return ctx.ms.getCacher(server) 57 | } 58 | 59 | // Producer return producer 60 | func (ctx *SchedulerContext) Producer(servers string) IProducer { 61 | return ctx.ms.getProducer(servers) 62 | } 63 | 64 | // MQ return MQ 65 | func (ctx *SchedulerContext) MQ(servers string) IMQ { 66 | return NewMQ(servers, ctx.ms) 67 | } 68 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1. Format of docker image is $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP 4 | # for example 3dsinteractive/automation-technology:prd-1.0.20210117202336 5 | APP_VERSION=1.0 6 | TIMESTAMP=20210117202336 7 | DEPLOY_ENV=prd 8 | DOCKER_REPOSITORY= 9 | 10 | # 2. commit will push docker image to repository 11 | function commit() { 12 | local IMAGE=$1 13 | echo "docker push image : $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP" 14 | docker push $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP 15 | } 16 | 17 | # 3. build_api is the main function to build Dockerfile 18 | function build_api() { 19 | local IMAGE=automation-technology 20 | 21 | # If found go in default path, it will use go from default path 22 | GO=/usr/local/go/bin/go 23 | if [ -f "$GO" ]; then 24 | /usr/local/go/bin/go mod init automationworkshop/main 25 | /usr/local/go/bin/go get 26 | /usr/local/go/bin/go mod vendor 27 | else 28 | go mod init automationworkshop/main 29 | go get 30 | go mod vendor 31 | fi 32 | 33 | # Build the Dockerfile 34 | docker build -f Dockerfile -t $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP . 35 | commit $IMAGE 36 | } 37 | 38 | # 4. Validate APP_VERSION must not empty 39 | if [ "$APP_VERSION" = "" ]; then 40 | echo -e "APP_VERSION cannot be blank" 41 | exit 1 42 | fi 43 | 44 | # 5. Validate TIMESTAMP must not empty 45 | if [ "$TIMESTAMP" = "" ]; then 46 | echo -e "TIMESTAMP cannot be blank" 47 | exit 1 48 | fi 49 | 50 | # 6. Validate DEPLOY_ENV must not empty 51 | if [ "$DEPLOY_ENV" == "" ]; then 52 | echo -e "DEPLOY_ENV cannot be blank" 53 | exit 1 54 | fi 55 | 56 | # 7. Validate DOCKER_REPOSITORY must not empty 57 | if [ "$DOCKER_REPOSITORY" == "" ]; then 58 | echo -e "DOCKER_REPOSITORY cannot be blank" 59 | exit 1 60 | fi 61 | 62 | # 8. Run main build process 63 | build_api -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # exec env file if exists, .env might be in /.env or /config/.env 4 | FILE=/.env ; [ -f $FILE ] && . $FILE 5 | FILE=/config/.env ; [ -f $FILE ] && . $FILE 6 | 7 | # start main program 8 | exec /main -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/k8s/00-databases/00-namespace.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: scale-service 5 | labels: 6 | name: scale-service 7 | module: Namespace -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/k8s/00-databases/01-redis.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: redis 5 | namespace: scale-service 6 | labels: 7 | name: redis 8 | spec: 9 | replicas: 1 10 | strategy: 11 | type: Recreate 12 | selector: 13 | matchLabels: 14 | name: redis 15 | template: 16 | metadata: 17 | labels: 18 | name: redis 19 | spec: 20 | containers: 21 | - name: redis 22 | image: 3dsinteractive/redis:5.0 23 | imagePullPolicy: Always 24 | ports: 25 | - name: redis6379 26 | containerPort: 6379 27 | env: 28 | - name: ALLOW_EMPTY_PASSWORD 29 | value: "yes" 30 | resources: 31 | requests: 32 | memory: 500Mi 33 | cpu: 200m 34 | limits: 35 | memory: 1Gi 36 | cpu: 500m 37 | livenessProbe: 38 | tcpSocket: 39 | port: 6379 40 | initialDelaySeconds: 30 41 | timeoutSeconds: 1 42 | periodSeconds: 300 43 | readinessProbe: 44 | tcpSocket: 45 | port: 6379 46 | initialDelaySeconds: 30 47 | timeoutSeconds: 1 48 | periodSeconds: 30 49 | failureThreshold: 5 50 | --- 51 | apiVersion: v1 52 | kind: Service 53 | metadata: 54 | name: redis 55 | namespace: scale-service 56 | labels: 57 | name: redis 58 | spec: 59 | selector: 60 | name: redis 61 | ports: 62 | - name: redis6379 63 | port: 6379 64 | targetPort: 6379 65 | protocol: TCP 66 | clusterIP: None -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/k8s/00-databases/04-client-util.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: client-util 5 | namespace: scale-service 6 | labels: 7 | name: client-util 8 | spec: 9 | containers: 10 | - name: client-util 11 | image: opcellent/util:2.0 12 | stdin: true 13 | tty: true 14 | imagePullPolicy: Always 15 | resources: 16 | requests: 17 | memory: 500Mi 18 | cpu: 200m 19 | limits: 20 | memory: 1Gi 21 | cpu: 500m 22 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/k8s/01-application/05-register-api.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: register-api 5 | namespace: scale-service 6 | labels: 7 | name: register-api 8 | spec: 9 | replicas: 2 10 | strategy: 11 | type: RollingUpdate 12 | rollingUpdate: 13 | maxUnavailable: 1 14 | maxSurge: 1 15 | selector: 16 | matchLabels: 17 | name: register-api 18 | template: 19 | metadata: 20 | labels: 21 | name: register-api 22 | spec: 23 | containers: 24 | - name: register-api 25 | image: 3dsinteractive/automation-technology:prd-1.0.20210117202336 26 | imagePullPolicy: Always 27 | readinessProbe: 28 | httpGet: 29 | path: /healthz 30 | port: 8080 31 | initialDelaySeconds: 10 32 | periodSeconds: 10 33 | timeoutSeconds: 3 34 | failureThreshold: 3 35 | livenessProbe: 36 | httpGet: 37 | path: /healthz 38 | port: 8080 39 | initialDelaySeconds: 60 40 | periodSeconds: 30 41 | timeoutSeconds: 30 42 | failureThreshold: 2 43 | env: 44 | - name: SERVICE_ID 45 | value: register-api 46 | - name: CACHE_SERVER 47 | value: redis:6379 48 | - name: MQ_SERVERS 49 | value: kfk1:9092,kfk2:9092,kfk3:9092 50 | ports: 51 | - name: api8080 52 | containerPort: 8080 53 | resources: 54 | requests: 55 | memory: 500Mi 56 | cpu: 200m 57 | limits: 58 | memory: 1Gi 59 | cpu: 500m 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: register-api 65 | namespace: scale-service 66 | labels: 67 | name: register-api 68 | spec: 69 | selector: 70 | name: register-api 71 | ports: 72 | - name: "api8080" 73 | port: 8080 74 | targetPort: 8080 75 | protocol: TCP 76 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/k8s/01-application/06-mail-consumer.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mail-consumer 5 | namespace: scale-service 6 | labels: 7 | name: mail-consumer 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | name: mail-consumer 13 | template: 14 | metadata: 15 | labels: 16 | name: mail-consumer 17 | spec: 18 | containers: 19 | - name: mail-consumer 20 | image: 3dsinteractive/automation-technology:prd-1.0.20210117202336 21 | imagePullPolicy: Always 22 | readinessProbe: 23 | httpGet: 24 | path: /healthz 25 | port: 8080 26 | initialDelaySeconds: 10 27 | periodSeconds: 10 28 | timeoutSeconds: 3 29 | failureThreshold: 3 30 | livenessProbe: 31 | httpGet: 32 | path: /healthz 33 | port: 8080 34 | initialDelaySeconds: 60 35 | periodSeconds: 30 36 | timeoutSeconds: 30 37 | failureThreshold: 2 38 | env: 39 | - name: SERVICE_ID 40 | value: mail-consumer 41 | - name: CACHE_SERVER 42 | value: redis:6379 43 | - name: MQ_SERVERS 44 | value: kfk1:9092,kfk2:9092,kfk3:9092 45 | ports: 46 | - name: api8080 47 | containerPort: 8080 48 | resources: 49 | requests: 50 | memory: 500Mi 51 | cpu: 200m 52 | limits: 53 | memory: 1Gi 54 | cpu: 500m -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/k8s/01-application/07-ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: ingress 5 | namespace: scale-service 6 | labels: 7 | name: ingress 8 | spec: 9 | rules: 10 | - host: kubernetes.docker.internal 11 | http: 12 | paths: 13 | - path: / 14 | backend: 15 | serviceName: register-api 16 | servicePort: 8080 -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/main.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | cfg := NewConfig() 11 | 12 | ms := NewMicroservice() 13 | ms.RegisterLivenessProbeEndpoint("/healthz") 14 | 15 | // 1. Read SERVICE_ID via environment variable to run each service in separate deployments 16 | serviceID := cfg.ServiceID() 17 | 18 | // 2. Finally, these 2 services will run in separate deployments, 19 | // so we could control how many replicas each services will be 20 | switch serviceID { 21 | case "register-api": 22 | startHTTP(ms, cfg) 23 | case "mail-consumer": 24 | startConsumer(ms, cfg) 25 | } 26 | 27 | ms.Start() 28 | } 29 | 30 | func startHTTP(ms *Microservice, cfg IConfig) { 31 | ms.POST("/citizen", func(ctx IContext) error { 32 | 33 | // 1. Read Input (Not using it right now, just for example) 34 | input := ctx.ReadInput() 35 | ctx.Log("POST: /citizen " + input) 36 | 37 | // 2. Generate citizenID and send it to MQ 38 | citizenID := randString() 39 | citizen := map[string]interface{}{ 40 | "citizen_id": citizenID, 41 | } 42 | prod := ctx.Producer(cfg.MQServers()) 43 | err := prod.SendMessage("when-citizen-has-registered", "", citizen) 44 | if err != nil { 45 | ctx.Log(err.Error()) 46 | return err 47 | } 48 | 49 | // 3. Response citizenID 50 | status := map[string]interface{}{ 51 | "status": "success", 52 | "citizen_id": citizenID, 53 | } 54 | ctx.Response(http.StatusOK, status) 55 | return nil 56 | }) 57 | } 58 | 59 | func startConsumer(ms *Microservice, cfg IConfig) { 60 | topic := "when-citizen-has-registered" 61 | groupID := "mail-consumer" 62 | timeout := time.Duration(-1) 63 | 64 | mq := NewMQ(cfg.MQServers(), ms) 65 | mq.CreateTopicR(topic, 5, 1, time.Hour*24*30) 66 | ms.Consume(cfg.MQServers(), topic, groupID, timeout, func(ctx IContext) error { 67 | msg := ctx.ReadInput() 68 | ctx.Log("Mail has sent to " + msg) 69 | return nil 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/microservice_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/confluentinc/confluent-kafka-go/kafka" 8 | ) 9 | 10 | func (ms *Microservice) consumeSingle(servers string, topic string, groupID string, readTimeout time.Duration, h ServiceHandleFunc) { 11 | c, err := ms.newKafkaConsumer(servers, groupID) 12 | if err != nil { 13 | return 14 | } 15 | 16 | defer c.Close() 17 | 18 | c.Subscribe(topic, nil) 19 | 20 | for { 21 | if readTimeout <= 0 { 22 | // readtimeout -1 indicates no timeout 23 | readTimeout = -1 24 | } 25 | 26 | msg, err := c.ReadMessage(readTimeout) 27 | if err != nil { 28 | kafkaErr, ok := err.(kafka.Error) 29 | if ok { 30 | if kafkaErr.Code() == kafka.ErrTimedOut { 31 | if readTimeout == -1 { 32 | // No timeout just continue to read message again 33 | continue 34 | } 35 | } 36 | } 37 | ms.Log("Consumer", err.Error()) 38 | ms.Stop() 39 | return 40 | } 41 | 42 | // Execute Handler 43 | h(NewConsumerContext(ms, string(msg.Value))) 44 | } 45 | } 46 | 47 | // Consume register service endpoint for Consumer service 48 | func (ms *Microservice) Consume(servers string, topic string, groupID string, readTimeout time.Duration, h ServiceHandleFunc) error { 49 | go ms.consumeSingle(servers, topic, groupID, readTimeout, h) 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/microservice_http.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "context" 6 | "time" 7 | 8 | "github.com/labstack/echo" 9 | ) 10 | 11 | // GET register service endpoint for HTTP GET 12 | func (ms *Microservice) GET(path string, h ServiceHandleFunc) { 13 | ms.echo.GET(path, func(c echo.Context) error { 14 | return h(NewHTTPContext(ms, c)) 15 | }) 16 | } 17 | 18 | // POST register service endpoint for HTTP POST 19 | func (ms *Microservice) POST(path string, h ServiceHandleFunc) { 20 | ms.echo.POST(path, func(c echo.Context) error { 21 | return h(NewHTTPContext(ms, c)) 22 | }) 23 | } 24 | 25 | // PUT register service endpoint for HTTP PUT 26 | func (ms *Microservice) PUT(path string, h ServiceHandleFunc) { 27 | ms.echo.PUT(path, func(c echo.Context) error { 28 | return h(NewHTTPContext(ms, c)) 29 | }) 30 | } 31 | 32 | // PATCH register service endpoint for HTTP PATCH 33 | func (ms *Microservice) PATCH(path string, h ServiceHandleFunc) { 34 | ms.echo.PATCH(path, func(c echo.Context) error { 35 | return h(NewHTTPContext(ms, c)) 36 | }) 37 | } 38 | 39 | // DELETE register service endpoint for HTTP DELETE 40 | func (ms *Microservice) DELETE(path string, h ServiceHandleFunc) { 41 | ms.echo.DELETE(path, func(c echo.Context) error { 42 | return h(NewHTTPContext(ms, c)) 43 | }) 44 | } 45 | 46 | // startHTTP will start HTTP service, this function will block thread 47 | func (ms *Microservice) startHTTP(exitChannel chan bool) error { 48 | // Caller can exit by sending value to exitChannel 49 | go func() { 50 | <-exitChannel 51 | ms.stopHTTP() 52 | }() 53 | return ms.echo.Start(":8080") 54 | } 55 | 56 | func (ms *Microservice) stopHTTP() { 57 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 58 | defer cancel() 59 | ms.echo.Shutdown(ctx) 60 | } 61 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/microservice_liveness.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "net/http" 6 | 7 | "github.com/labstack/echo" 8 | ) 9 | 10 | func (ms *Microservice) isCacherAlive() bool { 11 | if ms.cacher == nil { 12 | return true 13 | } 14 | 15 | ms.Log("MS", "Perform healthcheck on Cacher") 16 | err := ms.cacher.Healthcheck() 17 | if err != nil { 18 | return false 19 | } 20 | return true 21 | } 22 | 23 | func (ms *Microservice) isAlive() (bool, string) { 24 | isAlive := ms.isCacherAlive() 25 | if !isAlive { 26 | return false, "Cacher healthcheck failed" 27 | } 28 | 29 | return true, "" 30 | } 31 | 32 | func (ms *Microservice) responseProbeOK(resp *echo.Response) { 33 | resp.WriteHeader(http.StatusOK) 34 | resp.Write([]byte("ok")) 35 | } 36 | 37 | func (ms *Microservice) responseProbeFailed(resp *echo.Response, reason string) { 38 | errMsg := "Healthcheck failed because of " + reason 39 | resp.WriteHeader(http.StatusInternalServerError) 40 | resp.Write([]byte(errMsg)) 41 | } 42 | 43 | // RegisterLivenessProbeEndpoint register endpoint for liveness probe 44 | func (ms *Microservice) RegisterLivenessProbeEndpoint(path string) { 45 | ms.echo.GET(path, func(c echo.Context) error { 46 | ok, reason := ms.isAlive() 47 | if !ok { 48 | ms.responseProbeFailed(c.Response(), reason) 49 | return nil 50 | } 51 | ms.responseProbeOK(c.Response()) 52 | return nil 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/microservice_scheduler.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Schedule will run handler at timer period 10 | func (ms *Microservice) Schedule(timer time.Duration, h ServiceHandleFunc) chan bool /*exit channel*/ { 11 | 12 | // exitChan must be call exitChan <- true from caller to exit scheduler 13 | exitChan := make(chan bool, 1) 14 | go func() { 15 | t := time.NewTicker(timer) 16 | done := make(chan bool, 1) 17 | isExit := false 18 | isExitMutex := sync.Mutex{} 19 | 20 | go func() { 21 | <-exitChan 22 | isExitMutex.Lock() 23 | isExit = true 24 | isExitMutex.Unlock() 25 | // Stop Tick() and send done message to exit for loop below 26 | // Ref: From the documentation http://golang.org/pkg/time/#Ticker.Stop 27 | // Stop turns off a ticker. After Stop, no more ticks will be sent. 28 | // Stop does not close the channel, to prevent a read from the channel succeeding incorrectly. 29 | t.Stop() 30 | done <- true 31 | }() 32 | 33 | for { 34 | select { 35 | case execTime := <-t.C: 36 | isExitMutex.Lock() 37 | if isExit { 38 | isExitMutex.Unlock() 39 | // Done in the next round 40 | continue 41 | } 42 | isExitMutex.Unlock() 43 | 44 | now := time.Now() 45 | // The schedule that older than 10s, will be skip, because t.C is buffer at size 1 46 | diff := now.Sub(execTime).Seconds() 47 | if diff > 10 { 48 | continue 49 | } 50 | h(NewSchedulerContext(ms)) 51 | case <-done: 52 | return 53 | } 54 | } 55 | }() 56 | 57 | return exitChan 58 | } 59 | -------------------------------------------------------------------------------- /chapter03-deploy-scale-services/3.3-scale-services/util.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "math/rand" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var specialCharsRegex = regexp.MustCompile("[^a-zA-Z0-9]+") 13 | 14 | func randString() string { 15 | i := rand.Int() 16 | return fmt.Sprintf("%d", i) 17 | } 18 | 19 | func escapeName(tokens ...string) string { 20 | // Any name rules 21 | // - Lowercase only (for consistency) 22 | // - . (dot), _ (underscore), - (minus) can be used 23 | // - Max length = 250 24 | var b bytes.Buffer 25 | 26 | // Name result must be token1-token2-token3-token4 without special characters 27 | for i, token := range tokens { 28 | if len(token) == 0 { 29 | continue 30 | } 31 | 32 | token = strings.ToLower(token) 33 | 34 | cleanToken := specialCharsRegex.ReplaceAllString(token, "-") 35 | if i != 0 { 36 | b.WriteString("-") 37 | } 38 | b.WriteString(cleanToken) 39 | } 40 | 41 | name := b.String() 42 | // - Cannot start with -, _, + 43 | for true { 44 | if len(name) == 0 || name[0] != '-' { 45 | break 46 | } 47 | name = name[1:] 48 | } 49 | 50 | // - Cannot be longer than 250 characters (max len) 51 | if len(name) > 250 { 52 | name = name[0:250] 53 | } 54 | 55 | return name 56 | } 57 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 3dsinteractive/golang:1.14-alpine3.9-librdfkafka1.4.0 2 | 3 | WORKDIR /go/src/bitbucket.org/automationworkshop/main 4 | ADD . /go/src/bitbucket.org/automationworkshop/main 5 | RUN go build -mod vendor -i -tags "musl static_all" . 6 | 7 | # ================ 8 | FROM 3dsinteractive/alpine:3.9 9 | 10 | COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 11 | COPY --from=0 /go/src/bitbucket.org/automationworkshop/main/main /main 12 | 13 | ADD ./entrypoint.sh /entrypoint.sh 14 | 15 | RUN adduser -u 1001 -D -s /bin/sh -G ping 1001 16 | RUN chown 1001:1001 /entrypoint.sh 17 | RUN chown 1001:1001 /main 18 | 19 | RUN chmod +x /entrypoint.sh 20 | RUN chmod +x /main 21 | 22 | USER 1001 23 | 24 | EXPOSE 8080 25 | 26 | ENTRYPOINT ["/entrypoint.sh"] 27 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/config.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import "os" 5 | 6 | // IConfig is interface for application config 7 | type IConfig interface { 8 | ServiceID() string 9 | CacheServer() string 10 | MQServers() string 11 | CitizenRegisteredTopic() string 12 | CitizenConfirmedTopic() string 13 | CitizenValidationAPI() string 14 | CitizenDeliveryAPI() string 15 | BatchDeliverAPI() string 16 | } 17 | 18 | // Config implement IConfig 19 | type Config struct{} 20 | 21 | // NewConfig return new config instance 22 | func NewConfig() *Config { 23 | return &Config{} 24 | } 25 | 26 | // ServiceID return ID of service 27 | func (cfg *Config) ServiceID() string { 28 | return os.Getenv("SERVICE_ID") 29 | } 30 | 31 | // CacheServer return redis server 32 | func (cfg *Config) CacheServer() string { 33 | return os.Getenv("CACHE_SERVER") 34 | } 35 | 36 | // MQServers return Kafka servers 37 | func (cfg *Config) MQServers() string { 38 | return os.Getenv("MQ_SERVERS") 39 | } 40 | 41 | // CitizenRegisteredTopic return topic name for registered event 42 | func (cfg *Config) CitizenRegisteredTopic() string { 43 | return "when-citizen-has-registered" 44 | } 45 | 46 | // CitizenConfirmedTopic return topic name for confirmed event 47 | func (cfg *Config) CitizenConfirmedTopic() string { 48 | return "when-citizen-has-confirmed" 49 | } 50 | 51 | // CitizenValidationAPI return API to validate citizen information 52 | func (cfg *Config) CitizenValidationAPI() string { 53 | return "http://external-api:8080/3rd-party/validate" 54 | } 55 | 56 | // CitizenDeliveryAPI return API to request delivery citizen ID card 57 | func (cfg *Config) CitizenDeliveryAPI() string { 58 | return "http://external-api:8080/3rd-party/delivery" 59 | } 60 | 61 | // BatchDeliverAPI return API to batch delivery citizen ID card 62 | func (cfg *Config) BatchDeliverAPI() string { 63 | return "http://batch-ptask-api:8080/ptask/delivery" 64 | } 65 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/context.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import "time" 5 | 6 | // IContext is the context for service 7 | type IContext interface { 8 | Log(message string) 9 | Param(name string) string 10 | QueryParam(name string) string 11 | Response(responseCode int, responseData interface{}) 12 | ReadInput() string 13 | ReadInputs() []string 14 | 15 | // Time 16 | Now() time.Time 17 | 18 | // Dependency 19 | Cacher(server string) ICacher 20 | Producer(servers string) IProducer 21 | MQ(servers string) IMQ 22 | Requester(baseURL string, timeout time.Duration) IRequester 23 | } 24 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/context_scheduler.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // SchedulerContext implement IContext it is context for Consumer 12 | type SchedulerContext struct { 13 | ms *Microservice 14 | } 15 | 16 | // NewSchedulerContext is the constructor function for SchedulerContext 17 | func NewSchedulerContext(ms *Microservice) *SchedulerContext { 18 | return &SchedulerContext{ 19 | ms: ms, 20 | } 21 | } 22 | 23 | // Log will log a message 24 | func (ctx *SchedulerContext) Log(message string) { 25 | _, fn, line, _ := runtime.Caller(1) 26 | fns := strings.Split(fn, "/") 27 | fmt.Println("Scheduler:", fns[len(fns)-1], line, message) 28 | } 29 | 30 | // Param return parameter by name (empty in scheduler) 31 | func (ctx *SchedulerContext) Param(name string) string { 32 | return "" 33 | } 34 | 35 | // QueryParam return empty in scheduler 36 | func (ctx *SchedulerContext) QueryParam(name string) string { 37 | return "" 38 | } 39 | 40 | // ReadInput return message (return empty in scheduler) 41 | func (ctx *SchedulerContext) ReadInput() string { 42 | return "" 43 | } 44 | 45 | // ReadInputs return messages in batch (return nil in scheduler) 46 | func (ctx *SchedulerContext) ReadInputs() []string { 47 | return nil 48 | } 49 | 50 | // Response return response to client 51 | func (ctx *SchedulerContext) Response(responseCode int, responseData interface{}) { 52 | return 53 | } 54 | 55 | // Now return now 56 | func (ctx *SchedulerContext) Now() time.Time { 57 | return time.Now() 58 | } 59 | 60 | // Cacher return cacher 61 | func (ctx *SchedulerContext) Cacher(server string) ICacher { 62 | return ctx.ms.getCacher(server) 63 | } 64 | 65 | // Producer return producer 66 | func (ctx *SchedulerContext) Producer(servers string) IProducer { 67 | return ctx.ms.getProducer(servers) 68 | } 69 | 70 | // MQ return MQ 71 | func (ctx *SchedulerContext) MQ(servers string) IMQ { 72 | return NewMQ(servers, ctx.ms) 73 | } 74 | 75 | // Requester return Requester 76 | func (ctx *SchedulerContext) Requester(baseURL string, timeout time.Duration) IRequester { 77 | return NewRequester(baseURL, timeout, ctx.ms) 78 | } 79 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1. Format of docker image is $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP 4 | # for example 3dsinteractive/automation-technology:prd-1.0.20210118001006 5 | APP_VERSION=1.0 6 | TIMESTAMP=20210118001006 7 | DEPLOY_ENV=prd 8 | DOCKER_REPOSITORY=3dsinteractive 9 | 10 | # 2. commit will push docker image to repository 11 | function commit() { 12 | local IMAGE=$1 13 | echo "docker push image : $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP" 14 | docker push $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP 15 | } 16 | 17 | # 3. build_api is the main function to build Dockerfile 18 | function build_api() { 19 | local IMAGE=automation-technology 20 | 21 | # If found go in default path, it will use go from default path 22 | GO=/usr/local/go/bin/go 23 | if [ -f "$GO" ]; then 24 | /usr/local/go/bin/go mod init automationworkshop/main 25 | /usr/local/go/bin/go get 26 | /usr/local/go/bin/go mod vendor 27 | else 28 | go mod init automationworkshop/main 29 | go get 30 | go mod vendor 31 | fi 32 | 33 | # Build the Dockerfile 34 | docker build -f Dockerfile -t $DOCKER_REPOSITORY/$IMAGE:$DEPLOY_ENV-$APP_VERSION.$TIMESTAMP . 35 | commit $IMAGE 36 | } 37 | 38 | # 4. Validate APP_VERSION must not empty 39 | if [ "$APP_VERSION" = "" ]; then 40 | echo -e "APP_VERSION cannot be blank" 41 | exit 1 42 | fi 43 | 44 | # 5. Validate TIMESTAMP must not empty 45 | if [ "$TIMESTAMP" = "" ]; then 46 | echo -e "TIMESTAMP cannot be blank" 47 | exit 1 48 | fi 49 | 50 | # 6. Validate DEPLOY_ENV must not empty 51 | if [ "$DEPLOY_ENV" == "" ]; then 52 | echo -e "DEPLOY_ENV cannot be blank" 53 | exit 1 54 | fi 55 | 56 | # 7. Validate DOCKER_REPOSITORY must not empty 57 | if [ "$DOCKER_REPOSITORY" == "" ]; then 58 | echo -e "DOCKER_REPOSITORY cannot be blank" 59 | exit 1 60 | fi 61 | 62 | # 8. Run main build process 63 | build_api -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # exec env file if exists, .env might be in /.env or /config/.env 4 | FILE=/.env ; [ -f $FILE ] && . $FILE 5 | FILE=/config/.env ; [ -f $FILE ] && . $FILE 6 | 7 | # start main program 8 | exec /main -profiler=true -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/00-databases/00-namespace.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: tcir-app 5 | labels: 6 | name: tcir-app 7 | module: Namespace -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/00-databases/01-redis.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: redis 5 | namespace: tcir-app 6 | labels: 7 | name: redis 8 | spec: 9 | replicas: 1 10 | strategy: 11 | type: Recreate 12 | selector: 13 | matchLabels: 14 | name: redis 15 | template: 16 | metadata: 17 | labels: 18 | name: redis 19 | spec: 20 | containers: 21 | - name: redis 22 | image: 3dsinteractive/redis:5.0 23 | imagePullPolicy: Always 24 | ports: 25 | - name: redis6379 26 | containerPort: 6379 27 | env: 28 | - name: ALLOW_EMPTY_PASSWORD 29 | value: "yes" 30 | resources: 31 | requests: 32 | memory: 500Mi 33 | cpu: 200m 34 | limits: 35 | memory: 1Gi 36 | cpu: 500m 37 | livenessProbe: 38 | tcpSocket: 39 | port: 6379 40 | initialDelaySeconds: 30 41 | timeoutSeconds: 1 42 | periodSeconds: 300 43 | readinessProbe: 44 | tcpSocket: 45 | port: 6379 46 | initialDelaySeconds: 30 47 | timeoutSeconds: 1 48 | periodSeconds: 30 49 | failureThreshold: 5 50 | --- 51 | apiVersion: v1 52 | kind: Service 53 | metadata: 54 | name: redis 55 | namespace: tcir-app 56 | labels: 57 | name: redis 58 | spec: 59 | selector: 60 | name: redis 61 | ports: 62 | - name: redis6379 63 | port: 6379 64 | targetPort: 6379 65 | protocol: TCP 66 | clusterIP: None -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/00-databases/04-client-util.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: client-util 5 | namespace: tcir-app 6 | labels: 7 | name: client-util 8 | spec: 9 | containers: 10 | - name: client-util 11 | image: opcellent/util:2.0 12 | stdin: true 13 | tty: true 14 | imagePullPolicy: Always 15 | resources: 16 | requests: 17 | memory: 500Mi 18 | cpu: 200m 19 | limits: 20 | memory: 1Gi 21 | cpu: 500m 22 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/01-application/05-register-api.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: register-api 5 | namespace: tcir-app 6 | labels: 7 | name: register-api 8 | spec: 9 | replicas: 2 10 | strategy: 11 | type: RollingUpdate 12 | rollingUpdate: 13 | maxUnavailable: 1 14 | maxSurge: 1 15 | selector: 16 | matchLabels: 17 | name: register-api 18 | template: 19 | metadata: 20 | labels: 21 | name: register-api 22 | spec: 23 | containers: 24 | - name: register-api 25 | image: 3dsinteractive/automation-technology:prd-1.0.20210118001006 26 | imagePullPolicy: Always 27 | readinessProbe: 28 | httpGet: 29 | path: /healthz 30 | port: 8080 31 | initialDelaySeconds: 10 32 | periodSeconds: 10 33 | timeoutSeconds: 3 34 | failureThreshold: 3 35 | livenessProbe: 36 | httpGet: 37 | path: /healthz 38 | port: 8080 39 | initialDelaySeconds: 60 40 | periodSeconds: 30 41 | timeoutSeconds: 30 42 | failureThreshold: 2 43 | env: 44 | - name: SERVICE_ID 45 | value: register-api 46 | - name: CACHE_SERVER 47 | value: redis:6379 48 | - name: MQ_SERVERS 49 | value: kfk1:9092,kfk2:9092,kfk3:9092 50 | ports: 51 | - name: api8080 52 | containerPort: 8080 53 | resources: 54 | requests: 55 | memory: 500Mi 56 | cpu: 200m 57 | limits: 58 | memory: 1Gi 59 | cpu: 500m 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: register-api 65 | namespace: tcir-app 66 | labels: 67 | name: register-api 68 | spec: 69 | selector: 70 | name: register-api 71 | ports: 72 | - name: "api8080" 73 | port: 8080 74 | targetPort: 8080 75 | protocol: TCP 76 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/01-application/06-mail-consumer.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mail-consumer 5 | namespace: tcir-app 6 | labels: 7 | name: mail-consumer 8 | spec: 9 | replicas: 3 10 | selector: 11 | matchLabels: 12 | name: mail-consumer 13 | template: 14 | metadata: 15 | labels: 16 | name: mail-consumer 17 | spec: 18 | containers: 19 | - name: mail-consumer 20 | image: 3dsinteractive/automation-technology:prd-1.0.20210118001006 21 | imagePullPolicy: Always 22 | readinessProbe: 23 | httpGet: 24 | path: /healthz 25 | port: 8080 26 | initialDelaySeconds: 10 27 | periodSeconds: 10 28 | timeoutSeconds: 3 29 | failureThreshold: 3 30 | livenessProbe: 31 | httpGet: 32 | path: /healthz 33 | port: 8080 34 | initialDelaySeconds: 60 35 | periodSeconds: 30 36 | timeoutSeconds: 30 37 | failureThreshold: 2 38 | env: 39 | - name: SERVICE_ID 40 | value: mail-consumer 41 | - name: CACHE_SERVER 42 | value: redis:6379 43 | - name: MQ_SERVERS 44 | value: kfk1:9092,kfk2:9092,kfk3:9092 45 | ports: 46 | - name: api8080 47 | containerPort: 8080 48 | resources: 49 | requests: 50 | memory: 500Mi 51 | cpu: 200m 52 | limits: 53 | memory: 1Gi 54 | cpu: 500m -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/01-application/07-batch-scheduler.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: batch-scheduler 5 | namespace: tcir-app 6 | labels: 7 | name: batch-scheduler 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | name: batch-scheduler 13 | template: 14 | metadata: 15 | labels: 16 | name: batch-scheduler 17 | spec: 18 | containers: 19 | - name: batch-scheduler 20 | image: 3dsinteractive/automation-technology:prd-1.0.20210118001006 21 | imagePullPolicy: Always 22 | readinessProbe: 23 | httpGet: 24 | path: /healthz 25 | port: 8080 26 | initialDelaySeconds: 10 27 | periodSeconds: 10 28 | timeoutSeconds: 3 29 | failureThreshold: 3 30 | livenessProbe: 31 | httpGet: 32 | path: /healthz 33 | port: 8080 34 | initialDelaySeconds: 60 35 | periodSeconds: 30 36 | timeoutSeconds: 30 37 | failureThreshold: 2 38 | env: 39 | - name: SERVICE_ID 40 | value: batch-scheduler 41 | - name: CACHE_SERVER 42 | value: redis:6379 43 | - name: MQ_SERVERS 44 | value: kfk1:9092,kfk2:9092,kfk3:9092 45 | ports: 46 | - name: api8080 47 | containerPort: 8080 48 | resources: 49 | requests: 50 | memory: 500Mi 51 | cpu: 200m 52 | limits: 53 | memory: 1Gi 54 | cpu: 500m -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/01-application/08-batch-ptask-api.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: batch-ptask-api 5 | namespace: tcir-app 6 | labels: 7 | name: batch-ptask-api 8 | spec: 9 | replicas: 2 10 | strategy: 11 | type: RollingUpdate 12 | rollingUpdate: 13 | maxUnavailable: 1 14 | maxSurge: 1 15 | selector: 16 | matchLabels: 17 | name: batch-ptask-api 18 | template: 19 | metadata: 20 | labels: 21 | name: batch-ptask-api 22 | spec: 23 | containers: 24 | - name: batch-ptask-api 25 | image: 3dsinteractive/automation-technology:prd-1.0.20210118001006 26 | imagePullPolicy: Always 27 | readinessProbe: 28 | httpGet: 29 | path: /healthz 30 | port: 8080 31 | initialDelaySeconds: 10 32 | periodSeconds: 10 33 | timeoutSeconds: 3 34 | failureThreshold: 3 35 | livenessProbe: 36 | httpGet: 37 | path: /healthz 38 | port: 8080 39 | initialDelaySeconds: 60 40 | periodSeconds: 30 41 | timeoutSeconds: 30 42 | failureThreshold: 2 43 | env: 44 | - name: SERVICE_ID 45 | value: batch-ptask-api 46 | - name: CACHE_SERVER 47 | value: redis:6379 48 | - name: MQ_SERVERS 49 | value: kfk1:9092,kfk2:9092,kfk3:9092 50 | ports: 51 | - name: api8080 52 | containerPort: 8080 53 | resources: 54 | requests: 55 | memory: 500Mi 56 | cpu: 200m 57 | limits: 58 | memory: 1Gi 59 | cpu: 500m 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: batch-ptask-api 65 | namespace: tcir-app 66 | labels: 67 | name: batch-ptask-api 68 | spec: 69 | selector: 70 | name: batch-ptask-api 71 | ports: 72 | - name: "api8080" 73 | port: 8080 74 | targetPort: 8080 75 | protocol: TCP 76 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/01-application/09-batch-ptask-worker.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: batch-ptask-worker 5 | namespace: tcir-app 6 | labels: 7 | name: batch-ptask-worker 8 | spec: 9 | replicas: 3 10 | selector: 11 | matchLabels: 12 | name: batch-ptask-worker 13 | template: 14 | metadata: 15 | labels: 16 | name: batch-ptask-worker 17 | spec: 18 | containers: 19 | - name: batch-ptask-worker 20 | image: 3dsinteractive/automation-technology:prd-1.0.20210118001006 21 | imagePullPolicy: Always 22 | readinessProbe: 23 | httpGet: 24 | path: /healthz 25 | port: 8080 26 | initialDelaySeconds: 10 27 | periodSeconds: 10 28 | timeoutSeconds: 3 29 | failureThreshold: 3 30 | livenessProbe: 31 | httpGet: 32 | path: /healthz 33 | port: 8080 34 | initialDelaySeconds: 60 35 | periodSeconds: 30 36 | timeoutSeconds: 30 37 | failureThreshold: 2 38 | env: 39 | - name: SERVICE_ID 40 | value: batch-ptask-worker 41 | - name: CACHE_SERVER 42 | value: redis:6379 43 | - name: MQ_SERVERS 44 | value: kfk1:9092,kfk2:9092,kfk3:9092 45 | ports: 46 | - name: api8080 47 | containerPort: 8080 48 | resources: 49 | requests: 50 | memory: 500Mi 51 | cpu: 200m 52 | limits: 53 | memory: 1Gi 54 | cpu: 500m -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/01-application/10-external-mock-api.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: external-api 5 | namespace: tcir-app 6 | labels: 7 | name: external-api 8 | spec: 9 | replicas: 1 10 | strategy: 11 | type: RollingUpdate 12 | rollingUpdate: 13 | maxUnavailable: 1 14 | maxSurge: 1 15 | selector: 16 | matchLabels: 17 | name: external-api 18 | template: 19 | metadata: 20 | labels: 21 | name: external-api 22 | spec: 23 | containers: 24 | - name: external-api 25 | image: 3dsinteractive/automation-technology:prd-1.0.20210118001006 26 | imagePullPolicy: Always 27 | readinessProbe: 28 | httpGet: 29 | path: /healthz 30 | port: 8080 31 | initialDelaySeconds: 10 32 | periodSeconds: 10 33 | timeoutSeconds: 3 34 | failureThreshold: 3 35 | livenessProbe: 36 | httpGet: 37 | path: /healthz 38 | port: 8080 39 | initialDelaySeconds: 60 40 | periodSeconds: 30 41 | timeoutSeconds: 30 42 | failureThreshold: 2 43 | env: 44 | - name: SERVICE_ID 45 | value: external-api 46 | - name: CACHE_SERVER 47 | value: redis:6379 48 | - name: MQ_SERVERS 49 | value: kfk1:9092,kfk2:9092,kfk3:9092 50 | ports: 51 | - name: api8080 52 | containerPort: 8080 53 | resources: 54 | requests: 55 | memory: 500Mi 56 | cpu: 200m 57 | limits: 58 | memory: 1Gi 59 | cpu: 500m 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: external-api 65 | namespace: tcir-app 66 | labels: 67 | name: external-api 68 | spec: 69 | selector: 70 | name: external-api 71 | ports: 72 | - name: "api8080" 73 | port: 8080 74 | targetPort: 8080 75 | protocol: TCP 76 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/k8s/01-application/11-ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: ingress 5 | namespace: tcir-app 6 | labels: 7 | name: ingress 8 | spec: 9 | rules: 10 | - host: kubernetes.docker.internal 11 | http: 12 | paths: 13 | - path: /api 14 | backend: 15 | serviceName: register-api 16 | servicePort: 8080 -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/microservice_consumer.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/confluentinc/confluent-kafka-go/kafka" 8 | ) 9 | 10 | func (ms *Microservice) consumeSingle(servers string, topic string, groupID string, readTimeout time.Duration, h ServiceHandleFunc) { 11 | c, err := ms.newKafkaConsumer(servers, groupID) 12 | if err != nil { 13 | return 14 | } 15 | 16 | defer c.Close() 17 | 18 | c.Subscribe(topic, nil) 19 | 20 | for { 21 | if readTimeout <= 0 { 22 | // readtimeout -1 indicates no timeout 23 | readTimeout = -1 24 | } 25 | 26 | msg, err := c.ReadMessage(readTimeout) 27 | if err != nil { 28 | kafkaErr, ok := err.(kafka.Error) 29 | if ok { 30 | if kafkaErr.Code() == kafka.ErrTimedOut { 31 | if readTimeout == -1 { 32 | // No timeout just continue to read message again 33 | continue 34 | } 35 | } 36 | } 37 | ms.Log("Consumer", err.Error()) 38 | ms.Stop() 39 | return 40 | } 41 | 42 | // Execute Handler 43 | h(NewConsumerContext(ms, string(msg.Value))) 44 | } 45 | } 46 | 47 | // Consume register service endpoint for Consumer service 48 | func (ms *Microservice) Consume(servers string, topic string, groupID string, readTimeout time.Duration, h ServiceHandleFunc) error { 49 | go ms.consumeSingle(servers, topic, groupID, readTimeout, h) 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/microservice_http.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "context" 6 | "time" 7 | 8 | "github.com/labstack/echo" 9 | ) 10 | 11 | // GET register service endpoint for HTTP GET 12 | func (ms *Microservice) GET(path string, h ServiceHandleFunc) { 13 | ms.echo.GET(path, func(c echo.Context) error { 14 | return h(NewHTTPContext(ms, c)) 15 | }) 16 | } 17 | 18 | // POST register service endpoint for HTTP POST 19 | func (ms *Microservice) POST(path string, h ServiceHandleFunc) { 20 | ms.echo.POST(path, func(c echo.Context) error { 21 | return h(NewHTTPContext(ms, c)) 22 | }) 23 | } 24 | 25 | // PUT register service endpoint for HTTP PUT 26 | func (ms *Microservice) PUT(path string, h ServiceHandleFunc) { 27 | ms.echo.PUT(path, func(c echo.Context) error { 28 | return h(NewHTTPContext(ms, c)) 29 | }) 30 | } 31 | 32 | // PATCH register service endpoint for HTTP PATCH 33 | func (ms *Microservice) PATCH(path string, h ServiceHandleFunc) { 34 | ms.echo.PATCH(path, func(c echo.Context) error { 35 | return h(NewHTTPContext(ms, c)) 36 | }) 37 | } 38 | 39 | // DELETE register service endpoint for HTTP DELETE 40 | func (ms *Microservice) DELETE(path string, h ServiceHandleFunc) { 41 | ms.echo.DELETE(path, func(c echo.Context) error { 42 | return h(NewHTTPContext(ms, c)) 43 | }) 44 | } 45 | 46 | // startHTTP will start HTTP service, this function will block thread 47 | func (ms *Microservice) startHTTP(exitChannel chan bool) error { 48 | // Caller can exit by sending value to exitChannel 49 | go func() { 50 | <-exitChannel 51 | ms.stopHTTP() 52 | }() 53 | return ms.echo.Start(":8080") 54 | } 55 | 56 | func (ms *Microservice) stopHTTP() { 57 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 58 | defer cancel() 59 | ms.echo.Shutdown(ctx) 60 | } 61 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/microservice_liveness.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "net/http" 6 | 7 | "github.com/labstack/echo" 8 | ) 9 | 10 | func (ms *Microservice) isCacherAlive() bool { 11 | if ms.cacher == nil { 12 | return true 13 | } 14 | 15 | ms.Log("MS", "Perform healthcheck on Cacher") 16 | err := ms.cacher.Healthcheck() 17 | if err != nil { 18 | return false 19 | } 20 | return true 21 | } 22 | 23 | func (ms *Microservice) isAlive() (bool, string) { 24 | isAlive := ms.isCacherAlive() 25 | if !isAlive { 26 | return false, "Cacher healthcheck failed" 27 | } 28 | 29 | return true, "" 30 | } 31 | 32 | func (ms *Microservice) responseProbeOK(resp *echo.Response) { 33 | resp.WriteHeader(http.StatusOK) 34 | resp.Write([]byte("ok")) 35 | } 36 | 37 | func (ms *Microservice) responseProbeFailed(resp *echo.Response, reason string) { 38 | errMsg := "Healthcheck failed because of " + reason 39 | resp.WriteHeader(http.StatusInternalServerError) 40 | resp.Write([]byte(errMsg)) 41 | } 42 | 43 | // RegisterLivenessProbeEndpoint register endpoint for liveness probe 44 | func (ms *Microservice) RegisterLivenessProbeEndpoint(path string) { 45 | ms.echo.GET(path, func(c echo.Context) error { 46 | ok, reason := ms.isAlive() 47 | if !ok { 48 | ms.responseProbeFailed(c.Response(), reason) 49 | return nil 50 | } 51 | ms.responseProbeOK(c.Response()) 52 | return nil 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/microservice_scheduler.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Schedule will run handler at timer period 10 | func (ms *Microservice) Schedule(timer time.Duration, h ServiceHandleFunc) chan bool /*exit channel*/ { 11 | 12 | // exitChan must be call exitChan <- true from caller to exit scheduler 13 | exitChan := make(chan bool, 1) 14 | go func() { 15 | t := time.NewTicker(timer) 16 | done := make(chan bool, 1) 17 | isExit := false 18 | isExitMutex := sync.Mutex{} 19 | 20 | go func() { 21 | <-exitChan 22 | isExitMutex.Lock() 23 | isExit = true 24 | isExitMutex.Unlock() 25 | // Stop Tick() and send done message to exit for loop below 26 | // Ref: From the documentation http://golang.org/pkg/time/#Ticker.Stop 27 | // Stop turns off a ticker. After Stop, no more ticks will be sent. 28 | // Stop does not close the channel, to prevent a read from the channel succeeding incorrectly. 29 | t.Stop() 30 | done <- true 31 | }() 32 | 33 | for { 34 | select { 35 | case execTime := <-t.C: 36 | isExitMutex.Lock() 37 | if isExit { 38 | isExitMutex.Unlock() 39 | // Done in the next round 40 | continue 41 | } 42 | isExitMutex.Unlock() 43 | 44 | now := time.Now() 45 | // The schedule that older than 10s, will be skip, because t.C is buffer at size 1 46 | diff := now.Sub(execTime).Seconds() 47 | if diff > 10 { 48 | continue 49 | } 50 | h(NewSchedulerContext(ms)) 51 | case <-done: 52 | return 53 | } 54 | } 55 | }() 56 | 57 | return exitChan 58 | } 59 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/models.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | // Citizen is model for citizen 5 | type Citizen struct { 6 | CitizenID string `json:"citizen_id"` 7 | } 8 | -------------------------------------------------------------------------------- /chapter04-implement-reallife-app/4.2-tcir-application/util.go: -------------------------------------------------------------------------------- 1 | // Create and maintain by Chaiyapong Lapliengtrakul (chaiyapong@3dsinteractive.com), All right reserved (2021 - Present) 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "math/rand" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var specialCharsRegex = regexp.MustCompile("[^a-zA-Z0-9]+") 13 | 14 | func randString() string { 15 | i := rand.Int() 16 | return fmt.Sprintf("%d", i) 17 | } 18 | 19 | func escapeName(tokens ...string) string { 20 | // Any name rules 21 | // - Lowercase only (for consistency) 22 | // - . (dot), _ (underscore), - (minus) can be used 23 | // - Max length = 250 24 | var b bytes.Buffer 25 | 26 | // Name result must be token1-token2-token3-token4 without special characters 27 | for i, token := range tokens { 28 | if len(token) == 0 { 29 | continue 30 | } 31 | 32 | token = strings.ToLower(token) 33 | 34 | cleanToken := specialCharsRegex.ReplaceAllString(token, "-") 35 | if i != 0 { 36 | b.WriteString("-") 37 | } 38 | b.WriteString(cleanToken) 39 | } 40 | 41 | name := b.String() 42 | // - Cannot start with -, _, + 43 | for true { 44 | if len(name) == 0 || name[0] != '-' { 45 | break 46 | } 47 | name = name[1:] 48 | } 49 | 50 | // - Cannot be longer than 250 characters (max len) 51 | if len(name) > 250 { 52 | name = name[0:250] 53 | } 54 | 55 | return name 56 | } 57 | --------------------------------------------------------------------------------