add cicd part 2 practice
This commit is contained in:
Sergey
2021-12-14 18:57:25 +03:00
committed by GitHub
parent f905acf9c6
commit a37bfb6345
13 changed files with 715 additions and 0 deletions
@@ -0,0 +1,34 @@
# Создаем кластер
## 1. Создаем кластер в веб-интерфейсе VK Cloud Solutions
1 мастер из 2 cpu 4Gb RAM
2 узла и 1 cpu 2Gb RAM
Ждем пока кластер создасться и на вкладке подключение скачиваем конфиг для kubectl
## 2. объединяем два конфига
```bash
# Make a copy of your existing config
cp ~/.kube/config ~/.kube/config.bak
# Merge the two config files together into a new config file
KUBECONFIG=~/.kube/config:~/.kube/vkcs kubectl config view --flatten > ~/.kube/config.new
# Replace your old config with the new merged config
mv ~/.kube/config.new ~/.kube/config
# check config
kubectl config view
# (optional) Delete the backup once you confirm everything worked ok
rm ~/.kube/config.bak
```
## 3. Переключаем на нужный контекст
```bash
kubectl config get-context
kubectl config use-context default/kubernetes-cluster-6802
```
@@ -0,0 +1,8 @@
# Устанавливаем Базу Данных
## 1. Установка PostgreSQL
В качестве БД Postgres мы используем managed решение от облака.
Создаем инстанс Postgress, в нем создаем базу с названием xpaste
Пользователя с названием xpaste и задаем ему пароль.
Пароль запоминаем.
@@ -0,0 +1,66 @@
# Подготовка кластера
## 1. Создаем SA для helm
Для этого запускаем скрипт `setup.sh`.
```bash
bash setup.sh xpaste production
```
В конце своего выполнения скрипт выдаст нам токен, который необходимо сохранить.
## 2. Создание variables в gitlab
Для доступа из Gitlab в Kubernetes нам необходимо добавить в Gitlab переменную, в которой будет содержаться токен с предыдущего шага.
* Переходим в Gitlab
Для этого открываем в браузере свой форк xpaste.
* Добавляем переменную
Для этого в левом меню находим `Settings -> CI/CD -> Variables` и нажимаем Expand. Жмем кнопку `Add Variable` и в поле `Key` вводим имя переменной:
```bash
K8S_PROD_CI_TOKEN
```
В поле `Value` вводим скопированный токен из вывода команды setup.sh (пункт 1) и нажимаем `Add Variable`.
## 3. Создаем token для доступа в registry
Для этого переходим в раздел `Settings -> Repository -> Deploy tokens` и нажимаем Expand.
В поле `Name` вводим
```bash
k8s-pull-token-prod
```
Cтавим галочку рядом с `read_registry`. Все остальные поля оставляем пустыми. Нажимаем `Create deploy token`.
```!!НЕ ЗАКРЫВАЕМ ОКНО БРАУЗЕРА!!```
## 4. Создаем secret в kubernetes
Создаем secret, который будет использоваться для image pull. Для этого возвращаемся на первый master и выполняем команду:
Вносим нужные данные в скрипт `docker_pull_secret.sh` и запускаем его:
Соответственно подставляя на место `<>` нужные параметры, которые получили на `шаге 3`.
```bash
vim docker_pull_secret.sh
./docker_pull_secret.sh
```
## 5. Создание секрета для приложения
Создаем секрет, из которого при деплое будут взяты значения для переменных окружения, таких как доступы к БД и секретный ключ.
Вносим нужные данные в скрипт xpaste_secret.sh (в данном случае ничего не меняем) и запускаем его:
```bash
vim xpaste_secret.sh
./xpaste_secret.sh
```
`secret-key-base xxxxxxxxxxxxx` это не плэйсхолдер. Можно так и оставить.
@@ -0,0 +1,12 @@
#!/bin/bash
NS=xpaste-production
kubectl delete secret xpaste-gitlab-registry --namespace "$NS"
kubectl create secret docker-registry xpaste-gitlab-registry \
--docker-server registry.gitlab.com \
--docker-email 'student@slurm.io' \
--docker-username '<первая строчка из окна создания токена в gitlab>' \
--docker-password '<вторая строчка из окна создания токена в gitlab>' \
--namespace "$NS"
+95
View File
@@ -0,0 +1,95 @@
#!/bin/bash
CI_PROJECT_PATH_SLUG=$1
CI_ENVIRONMENT_NAME=$2
GREEN='\033[0;32m'
NC='\033[0m'
usage() {
echo "Usage: $0 CI_PROJECT_PATH_SLUG CI_ENVIRONMENT_NAME"
}
base64_decode_key() {
if [[ "$OSTYPE" == "linux"* ]]; then
echo "-d"
elif [[ "$OSTYPE" == "darwin"* ]]; then
echo "-D"
else
echo "--help"
fi
}
if [ -z "$CI_PROJECT_PATH_SLUG" ] || [ -z "$CI_ENVIRONMENT_NAME" ]; then
usage
exit 1
fi
NS="$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_NAME"
SA="$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_NAME"
ROLE="$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_NAME"
ROLEBIND="$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_NAME"
if kubectl get ns "$NS"; then
echo -e "${GREEN}namespace for project already exists${NC}"
else
echo -e "${GREEN}creating namespace for project${NC}"
kubectl create namespace "$NS"
echo
fi
if kubectl -n "$NS" get sa "$SA"; then
echo -e "${GREEN}serviceaccount for project already exists${NC}"
else
echo
echo -e "${GREEN}creating CI serviceaccount for project${NC}"
kubectl create serviceaccount \
--namespace "$NS" \
"$SA"
echo
fi
if kubectl -n "$NS" get role "$ROLE"; then
echo -e "${GREEN}role for project already exists${NC}"
else
echo -e "${GREEN}creating CI role for project${NC}"
cat << EOF | kubectl apply --namespace $NS -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: "$ROLE"
rules:
- apiGroups: ["", "extensions", "apps", "batch", "events", "networking.k8s.io", "certmanager.k8s.io", "cert-manager.io", "monitoring.coreos.com"]
resources: ["*"]
verbs: ["*"]
EOF
echo
fi
if kubectl -n "$NS" get rolebinding "$ROLEBIND"; then
echo -e "${GREEN}rolebinding for project already exists${NC}"
else
echo -e "${GREEN}creating CI rolebinding for project${NC}"
kubectl create rolebinding \
--namespace "$NS" \
--serviceaccount "$NS":"$SA" \
--role "$ROLE" \
"$ROLEBIND"
echo
fi
echo -e "${GREEN}access token for new CI user:${NC}"
kubectl get secret \
--namespace "$NS" \
$( \
kubectl get serviceaccount \
--namespace "$NS" \
"$SA" \
-o jsonpath='{.secrets[].name}'\
) \
-o jsonpath='{.data.token}' | base64 $(base64_decode_key)
echo
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
NS=xpaste-production
kubectl delete secret slurm-xpaste --namespace "$NS"
kubectl create secret generic slurm-xpaste \
--from-literal secret-key-base=xxxxxxxxxxxxxxxxxxxxxxxxx \
--from-literal db-user='xpaste' \
--from-literal db-password='xpaste1234567890' \
--namespace "$NS"
@@ -0,0 +1,94 @@
variables:
K8S_DEV_API_URL: https://kubernetes.default
K8S_PROD_API_URL: https://ip.add.re.ss:6443
NAMESPACE: xpaste
default:
tags:
- slurm-k8s
stages:
- linter
- build
- template
- deploy
build:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug-v1.3.0
entrypoint: [""]
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- |
/kaniko/executor --context $CI_PROJECT_DIR \
--cache=true --cache-repo=$CI_REGISTRY_IMAGE \
--dockerfile $CI_PROJECT_DIR/Dockerfile \
--destination ${CI_REGISTRY_IMAGE}:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
deploy:dev:
stage: deploy
image: centosadmin/kubernetes-helm:3.6.3
resource_group: deploy_dev
environment:
name: development
script:
- kubectl config set-cluster k8s --insecure-skip-tls-verify=true --server=$K8S_DEV_API_URL
- kubectl config set-credentials ci --token=$K8S_DEV_CI_TOKEN
- kubectl config set-context ci --cluster=k8s --user=ci
- kubectl config use-context ci
- helm upgrade --install xpaste .helm
--set image=$CI_REGISTRY_IMAGE
--set imageTag=$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
--debug
--atomic
--timeout 120s
--namespace $NAMESPACE-$CI_ENVIRONMENT_SLUG
only:
- master
helm_lint:
stage: linter
image: centosadmin/kubernetes-helm:3.6.3
environment:
name: production
script:
- helm lint .helm
--set image=$CI_REGISTRY_IMAGE
--set imageTag=$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
only:
- master
template:
stage: template
image: centosadmin/kubernetes-helm:3.6.3
environment:
name: production
script:
- helm template $CI_PROJECT_PATH_SLUG .helm
--set image=$CI_REGISTRY_IMAGE
--set imageTag=$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
only:
- master
deploy:prod:
stage: deploy
image: centosadmin/kubernetes-helm:3.6.3
resource_group: deploy_prod
environment:
name: production
script:
- kubectl config set-cluster k8s --insecure-skip-tls-verify=true --server=$K8S_PROD_API_URL
- kubectl config set-credentials ci --token=$K8S_PROD_CI_TOKEN
- kubectl config set-context ci --cluster=k8s --user=ci
- kubectl config use-context ci
- helm upgrade --install xpaste .helm
--set image=$CI_REGISTRY_IMAGE
--set imageTag=$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
--debug
--atomic
--timeout 120s
--namespace $NAMESPACE-$CI_ENVIRONMENT_SLUG
only:
- master
+56
View File
@@ -0,0 +1,56 @@
# Деплой на production
## 1. Добавляем переменные и шаг деплоя на production кластер
```yaml
variables:
K8S_PROD_API_URL: https://ip.add.re.ss:6443
```
```yaml
deploy:prod:
stage: deploy
image: centosadmin/kubernetes-helm:3.6.3
environment:
name: production
script:
- kubectl config set-cluster k8s --insecure-skip-tls-verify=true --server=$K8S_PROD_API_URL
- kubectl config set-credentials ci --token=$K8S_PROD_CI_TOKEN
- kubectl config set-context ci --cluster=k8s --user=ci
- kubectl config use-context ci
- helm upgrade --install xpaste .helm
-f .helm/values.prod.yaml
--set image=$CI_REGISTRY_IMAGE
--set imageTag=$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
--debug
--atomic
--timeout 120s
--namespace $NAMESPACE-$CI_ENVIRONMENT_SLUG
only:
- master
```
Или копируем готовый файл `.gitlab-ci.yml` в репозиторий xpaste.
## 2. Создаем production настройки для чарта
```bash
cd .helm
cp values.yaml values.prod.yaml
vi values.prod.yaml
```
Исправляем адрес БД в .helm/values.prod.yaml
```
env:
DB_HOST: 10.0.0.19
```
Исправляем host в ingress
```
ingress:
host: xpaste.s000005.vkcs.slurm.io
```
Пушим, смотрим результат.
@@ -0,0 +1,25 @@
# Запускаем приложение в несколько реплик
## 1. Копируем манифест deployment
```
cp deployment.yaml ~/xpaste/.helm/templates/
```
## 2. Пушим, ждем запуска, рассматриваем
```
cd ~/xpaste
git add .helm/templates/deployment.yaml
git commit -m "Run app with nginx in separate containers"
git push
```
## 3. Смотрим описание пода, список запущенных процессов
```
kubectl describe pod ...
kubectl exec -it ...
ps ax
```
@@ -0,0 +1,120 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: {{ .Chart.Name }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
component: app
name: {{ .Release.Name }}
spec:
progressDeadlineSeconds: 180
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ .Chart.Name }}
release: {{ .Release.Name }}
component: app
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
revisionHistoryLimit: 5
template:
metadata:
creationTimestamp: null
labels:
app: {{ .Chart.Name }}
release: {{ .Release.Name }}
component: app
spec:
containers:
- name: nginx
command: ["/usr/sbin/nginx"]
args: ["-g", "daemon off;"]
image: {{ .Values.image }}:{{ .Values.imageTag }}
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /var/run
name: socket
ports:
- containerPort: {{ .Values.service.port }}
name: http
protocol: TCP
readinessProbe:
failureThreshold: 30
httpGet:
path: /
port: {{ .Values.service.port }}
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
livenessProbe:
initialDelaySeconds: 90
failureThreshold: 3
httpGet:
path: /
port: {{ .Values.service.port }}
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
resources:
limits:
cpu: 100m
memory: 256Mi
requests:
cpu: 100m
memory: 256Mi
- env:
{{- range $key, $val := .Values.env }}
- name: {{ $key | quote }}
value: {{ $val | quote }}
{{- end }}
{{- range $key, $val := .Values.envSecret }}
- name: {{ $key | quote }}
valueFrom:
secretKeyRef:
key: {{ $key | lower | replace "_" "-" }}
name: {{ $val }}
{{- end }}
image: {{ .Values.image }}:{{ .Values.imageTag}}
imagePullPolicy: IfNotPresent
name: app
command: ["/bin/sh"]
args: ["-c","bundle exec rake db:migrate && exec bundle exec puma -b unix:///var/run/puma.sock -e $RAILS_ENV config.ru"]
volumeMounts:
- mountPath: /var/run
name: socket
readinessProbe:
failureThreshold: 30
httpGet:
path: /
port: {{ .Values.service.port }}
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
livenessProbe:
initialDelaySeconds: 90
failureThreshold: 3
httpGet:
path: /
port: {{ .Values.service.port }}
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
resources:
{{ toYaml .Values.resources | indent 12 }}
dnsPolicy: ClusterFirst
restartPolicy: Always
terminationGracePeriodSeconds: 20
imagePullSecrets:
- name: {{ .Values.imagePullSecret }}
volumes:
- name: socket
emptyDir: {}
@@ -0,0 +1,18 @@
# Добавляем pre-install hook с миграцией БД
## 1. Копируем манифест deployment и job.migrate
```
cp job.migrate.yaml ~/xpaste/.helm/templates/
cp deployment.yaml ~/xpaste/.helm/templates/
```
## 2. Пушим, ждем запуска, рассматриваем
```
cd ~/xpaste
git add .helm/templates/job.migrate.yaml
git add .helm/templates/deployment.yaml
git commit -m "Add migrate job"
git push
```
@@ -0,0 +1,120 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: {{ .Chart.Name }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
component: app
name: {{ .Release.Name }}
spec:
progressDeadlineSeconds: 180
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: {{ .Chart.Name }}
release: {{ .Release.Name }}
component: app
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
revisionHistoryLimit: 5
template:
metadata:
creationTimestamp: null
labels:
app: {{ .Chart.Name }}
release: {{ .Release.Name }}
component: app
spec:
containers:
- name: nginx
command: ["/usr/sbin/nginx"]
args: ["-g", "daemon off;"]
image: {{ .Values.image }}:{{ .Values.imageTag }}
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /var/run
name: socket
ports:
- containerPort: {{ .Values.service.port }}
name: http
protocol: TCP
readinessProbe:
failureThreshold: 30
httpGet:
path: /
port: {{ .Values.service.port }}
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
livenessProbe:
initialDelaySeconds: 90
failureThreshold: 3
httpGet:
path: /
port: {{ .Values.service.port }}
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
resources:
limits:
cpu: 100m
memory: 256Mi
requests:
cpu: 100m
memory: 256Mi
- env:
{{- range $key, $val := .Values.env }}
- name: {{ $key | quote }}
value: {{ $val | quote }}
{{- end }}
{{- range $key, $val := .Values.envSecret }}
- name: {{ $key | quote }}
valueFrom:
secretKeyRef:
key: {{ $key | lower | replace "_" "-" }}
name: {{ $val }}
{{- end }}
image: {{ .Values.image }}:{{ .Values.imageTag}}
imagePullPolicy: IfNotPresent
name: app
command: ["/bin/sh"]
args: ["-c","exec bundle exec puma -b unix:///var/run/puma.sock -e $RAILS_ENV config.ru"]
volumeMounts:
- mountPath: /var/run
name: socket
readinessProbe:
failureThreshold: 30
httpGet:
path: /
port: {{ .Values.service.port }}
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
livenessProbe:
initialDelaySeconds: 90
failureThreshold: 3
httpGet:
path: /
port: {{ .Values.service.port }}
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
resources:
{{ toYaml .Values.resources | indent 12 }}
dnsPolicy: ClusterFirst
restartPolicy: Always
terminationGracePeriodSeconds: 20
imagePullSecrets:
- name: {{ .Values.imagePullSecret }}
volumes:
- name: socket
emptyDir: {}
@@ -0,0 +1,56 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-dbmigrate"
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "1"
"helm.sh/hook-delete-policy": before-hook-creation
labels:
app: {{ .Chart.Name }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
component: dbmigrate
spec:
activeDeadlineSeconds: 180
template:
metadata:
labels:
app: {{ .Chart.Name }}
release: {{ .Release.Name }}
component: dbmigrate
spec:
containers:
- name: dbmigrate
image: {{ .Values.image }}:{{ .Values.imageTag }}
imagePullPolicy: IfNotPresent
command:
- bundle
args:
- exec
- rake
- db:migrate
env:
{{- range $key, $val := .Values.env }}
- name: {{ $key | quote }}
value: {{ $val | quote }}
{{- end }}
{{- range $key, $val := .Values.envSecret }}
- name: {{ $key | quote }}
valueFrom:
secretKeyRef:
key: {{ $key | lower | replace "_" "-" }}
name: {{ $val }}
{{- end }}
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 128Mi
dnsPolicy: ClusterFirst
restartPolicy: Never
imagePullSecrets:
- name: {{ .Values.imagePullSecret }}