03/02/2022 - DOCKER, GIT, GO, HELM, KUBERNETES
Bu örnekte, Golang uygulamamızı Helm kullanarak bir Kubernetes kümesine dağıtmak için GitHub eylemlerini kullanacağız. Yerel bir Minikube kümesi kullanacağım, bu yüzden onu Ngrok ile herkese açık hale getirmek için aşağıda bir "kurulum" bölümü olacak.
Aşağıda listelendiği gibi yerel Kubernetes kurulumum var ama biz sadece nonprod
kümesi ve develop
ad alanı ile çalışacağız.
CLUSTER NAMESPACES
prod default
nonprod develop, test, sandbox
├── Makefile
├── .dockerignore
├── .gitignore
├── .github
│ └── workflows
│ ├── cd.yaml
│ └── ci.yaml
├── infra
│ ├── docker
│ │ └── Dockerfile
│ └── helm
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── templates
│ │ ├── configmap.yaml
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ └── values.yaml
├── main.go
└── main_test.go
Bu yalnızca yerel kullanım içindir.
TAG := $(shell git rev-parse --short HEAD)
.PHONY: help
help: ## Display available commands.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
# LOCAL ------------------------------------------------------------------------
.PHONY: build
build: ## Build service binary
go build -race -ldflags "-s -w -X main.ver=${TAG}" -o rest main.go
.PHONY: run
run: ## Run service
HTTP_ADDR=:8080 ./rest
# DOCKER -----------------------------------------------------------------------
.PHONY: docker-push
docker-push: ## Build, tag and push service image to registry then clean up local environment
docker build --build-arg VER=${TAG} -t you/rest:${TAG} -f infra/docker/Dockerfile .
docker push you/rest:${TAG}
docker rmi you/rest:${TAG}
docker system prune --volumes --force
# DEPLOY -----------------------------------------------------------------------
.PHONY: helm-deploy
helm-deploy: ## Deploy go applications.
helm upgrade --install --atomic --timeout 1m rest infra/helm/ -f infra/helm/values.yaml \
--kube-context nonprod --namespace develop --create-namespace \
--set image.tag=${TAG}
.dockerignore
.gitignore
*.md
Makefile
rest
infra/
.git/
.idea/
.DS_Store/
rest
.DS_Store
.idea/
package main
import (
"log"
"net/http"
"os"
)
var ver string
func main() {
rtr := http.DefaultServeMux
rtr.HandleFunc("/", home{}.handle)
addr := os.Getenv("HTTP_ADDR")
log.Printf("%s: info: http listen and serve: %s", ver, addr)
if err := http.ListenAndServe(addr, rtr); err != nil && err != http.ErrServerClosed {
log.Printf("%s: error: http listen and serve: %s", ver, err)
}
}
type home struct{}
func (h home) handle(w http.ResponseWriter, r *http.Request) {
log.Printf("%s: info: X-Request-ID: %s\n", ver, r.Header.Get("X-Request-ID"))
_, _ = w.Write([]byte(ver))
}
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func Test_home_handle(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
res := httptest.NewRecorder()
home{}.handle(res, req)
if res.Code != http.StatusOK {
t.Error("expected 200 but got", res.Code)
}
}
FROM golang:1.17.5-alpine3.15 as build
WORKDIR /source
COPY . .
ARG VER
RUN CGO_ENABLED=0 go build -ldflags "-s -w -X main.ver=${VER}" -o rest main.go
FROM alpine:3.15
COPY --from=build /source/rest /rest
EXPOSE 8080
ENTRYPOINT ["./rest"]
.DS_Store
.git/
.gitignore
.idea/
*.md
apiVersion: v2
name: rest
type: application
icon: https://
description: This is an HTTP API
version: 0.0.1
namespace: default
env:
HTTP_ADDR: :8080
image:
name: you/rest
tag: latest
pull: IfNotPresent
deployment:
timestamp: 2006-01-02T15:04:05
replicas: 1
container:
name: go
port: 8080
service:
type: ClusterIP
port: 8080
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
namespace: {{ .Release.Namespace | default .Values.namespace }}
data:
HTTP_ADDR: {{ .Values.env.HTTP_ADDR }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
namespace: {{ .Release.Namespace | default .Values.namespace }}
labels:
app: {{ .Release.Name }}
spec:
replicas: {{ .Values.deployment.replicas }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
annotations:
timestamp: {{ now | date .Values.deployment.timestamp }}
spec:
containers:
- name: {{ .Values.deployment.container.name }}
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pull }}
ports:
- containerPort: {{ .Values.deployment.container.port }}
envFrom:
- configMapRef:
name: {{ .Release.Name }}
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
namespace: {{ .Release.Namespace | default .Values.namespace }}
spec:
type: {{ .Values.service.type }}
selector:
app: {{ .Release.Name }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.deployment.container.port }}
# Trigger the workflow to deploy to "nonprod" cluster using "develop" environment only when:
# - an existing pull request with any name/type is merged to the master or develop branch
# - a commit is directly pushed to the master or develop branch
name: Continuous Deployment
on:
push:
branches:
- master
- develop
jobs:
setup:
runs-on: ubuntu-latest
outputs:
repo: ${{ steps.vars.outputs.repo }}
tag: ${{ steps.vars.outputs.tag }}
steps:
- name: Use repository
uses: actions/checkout@v2
- name: Build variables
id: vars
run: |
echo "::set-output name=repo::$GITHUB_REPOSITORY"
echo "::set-output name=tag::$(git rev-parse --short "$GITHUB_SHA")"
- name: Upload repository
uses: actions/upload-artifact@v2
with:
name: repository
path: |
${{ github.workspace }}/infra
${{ github.workspace }}/.dockerignore
${{ github.workspace }}/main.go
${{ github.workspace }}/main_test.go
${{ github.workspace }}/go.mod
${{ github.workspace }}/go.sum
test:
needs: setup
runs-on: ubuntu-latest
steps:
- name: Use Golang 1.17
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: Download repository
uses: actions/download-artifact@v2
with:
name: repository
- name: Run tests
run: go test -v -race -timeout=180s -count=1 -cover ./...
docker:
needs: [setup, test]
runs-on: ubuntu-latest
steps:
- name: Download repository
uses: actions/download-artifact@v2
with:
name: repository
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v2
with:
push: true
file: ./infra/docker/Dockerfile
tags: ${{ needs.setup.outputs.repo }}:${{ needs.setup.outputs.tag }}
build-args: VER=${{ needs.setup.outputs.tag }}
deploy:
needs: [setup, docker]
runs-on: ubuntu-latest
steps:
- name: Download repository
uses: actions/download-artifact@v2
with:
name: repository
- name: Create kube config
run: |
mkdir -p $HOME/.kube/
echo "${{ secrets.KUBE_CONFIG }}" > $HOME/.kube/config
chmod 600 $HOME/.kube/config
- name: Install helm
run: |
curl -LO https://get.helm.sh/helm-v3.8.0-linux-amd64.tar.gz
tar -zxvf helm-v3.8.0-linux-amd64.tar.gz
mv linux-amd64/helm /usr/local/bin/helm
helm version
- name: Lint helm charts
run: helm lint ./infra/helm/
- name: Deploy
run: |
helm upgrade --install --atomic --timeout 1m rest ./infra/helm/ -f ./infra/helm/values.yaml \
--kube-context nonprod --namespace develop --create-namespace \
--set image.tag=${{ needs.setup.outputs.tag }}
# Trigger the workflow only when:
# - a new pull request with any name/type is opened against the master, develop, hotfix/* or release/* branch
# - a commit is directly pushed to the pull request
name: Continuous Integration
on:
pull_request:
branches:
- master
- develop
- hotfix/*
- release/*
jobs:
setup:
runs-on: ubuntu-latest
steps:
- name: Use repository
uses: actions/checkout@v2
- name: Upload repository
uses: actions/upload-artifact@v2
with:
name: repository
path: |
${{ github.workspace }}/main.go
${{ github.workspace }}/main_test.go
${{ github.workspace }}/go.mod
${{ github.workspace }}/go.sum
test:
needs: setup
runs-on: ubuntu-latest
steps:
- name: Use Golang 1.17
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: Download repository
uses: actions/download-artifact@v2
with:
name: repository
- name: Run tests
run: go test -v -race -timeout=180s -count=1 -cover ./...
Helm'in Kubernetes dosyalarını nasıl yorumladığını görmek için aşağıdaki komutu çalıştırın.
$ helm template ./infra/helm/
---
# Source: rest/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name
namespace: default
data:
HTTP_ADDR: :8080
---
# Source: rest/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: release-name
namespace: default
spec:
type: ClusterIP
selector:
app: release-name
ports:
- port: 8080
targetPort: 8080
---
# Source: rest/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: release-name
namespace: default
labels:
app: release-name
spec:
replicas: 1
selector:
matchLabels:
app: release-name
template:
metadata:
labels:
app: release-name
annotations:
timestamp: 2022-02-03T09:58:35
spec:
containers:
- name: go
image: "you/rest:latest"
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: release-name
Bu, iş akışınızın görüntüleri DockerHub kayıt defterine göndermesini sağlar.
DOCKERHUB_USERNAME
DOCKERHUB_TOKEN
Bu, iş akışlarınızın uygulamayı yerel K8S kümelerinize dağıtmasına olanak tanır.
// Enable routing public requests to K8S API.
$ kubectl proxy --disable-filter=true
Starting to serve on 127.0.0.1:8001
(Optionally $ kubectl proxy --port=8001 --accept-hosts='.*\.ngrok.io')
// Expose kubectl proxy to the Internet with ngrok.
$ ./ngrok http 127.0.0.1:8001
Forwarding http://117b-2a02-c7f-e84f-c900-85c1-38ee-a128-9cec.ngrok.io
Forwarding https://117b-2a02-c7f-e84f-c900-85c1-38ee-a128-9cec.ngrok.io
// Create a modified copy of your local kube config.
$ kubectl config view --flatten > ~/Desktop/kube_config
Remove `certificate-authority-data` line
Add `insecure-skip-tls-verify: true` line
Replace `server` value to `https://117b-2a02-c7f-e84f-c900-85c1-38ee-a128-9cec.ngrok.io`
Finally it should look like something like below.
```
apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://117b-2a02-c7f-e84f-c900-85c1-38ee-a128-9cec.ngrok.io
name: nonprod
- cluster:
insecure-skip-tls-verify: true
server: https://117b-2a02-c7f-e84f-c900-85c1-38ee-a128-9cec.ngrok.io
name: prod
contexts:
- context:
cluster: nonprod
user: nonprod
name: nonprod
- context:
cluster: prod
user: prod
name: prod
current-context: nonprod
kind: Config
preferences: {}
users:
- name: nonprod
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0F...==
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVp...=
- name: prod
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0F...==
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVk...=
```
Copy this content into `KUBE_CONFIG` GitHub secret.
Son olarak, GitHub'da bu sırlara sahip olmalısınız.
DOCKERHUB_USERNAME
DOCKERHUB_TOKEN
KUBE_CONFIG
$ minikube start -p prod --vm-driver=virtualbox
$ minikube start -p nonprod --vm-driver=virtualbox
$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* nonprod nonprod nonprod
prod prod prod
$ helm list --kube-context nonprod --all-namespaces
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
rest develop 2 2022-02-02 18:07:38.982227388 +0000 UTC deployed rest-0.0.1
$ kubectl --context nonprod --namespace develop get all
NAME READY STATUS RESTARTS AGE
pod/rest-749fcd6464-54n8r 1/1 Running 0 5h29m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/rest ClusterIP 10.100.20.2078080/TCP 5h43m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/rest 1/1 1 1 5h43m
NAME DESIRED CURRENT READY AGE
replicaset.apps/rest-749fcd6464 1 1 1 5h29m
$ kubectl --context nonprod --namespace develop port-forward service/rest 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
$ curl --request GET 'http://localhost:8080/' --header 'X-Request-ID: 123'
f11148a
$ kubectl --context nonprod --namespace develop logs -f service/rest
2022/02/02 18:08:00 f11148a: info: http listen and serve: :8080
2022/02/03 10:27:51 f11148a: info: X-Request-ID: 123