• 2 ноды g1-small (1,5 ГБ) • 1 нода n1-standard-2 (7,5 ГБ) В настройках: • Stackdriver Logging - Отключен • Stackdriver Monitoring - Отключен • Устаревшие права доступа - Включено
gcloud container clusters get-credentials cluster-1 --zone us-central1-a --project docker-194414
helm init
helm ls
output Error: Get http://localhost:8080/api/v1/namespaces/kube-system/configmaps?labelSelector=OWNER%!D(MISSING)TILLER: dial tcp [::1]:8080: connect: connection refused
helm init --upgrade
output $HELM_HOME has been configured at /home/asomir/.helm. Tiller (the Helm server-side component) has been upgraded to the current version.
kubectl --namespace=kube-system edit deployment/tiller-deploy
and changed automountServiceAccountToken to true
helm install stable/nginx-ingress --name nginx
NAME: nginx
LAST DEPLOYED: Sun Apr 29 23:41:07 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Service
NAME AGE
nginx-nginx-ingress-controller 2s
nginx-nginx-ingress-default-backend 2s
==> v1beta1/Deployment
nginx-nginx-ingress-controller 2s
nginx-nginx-ingress-default-backend 2s
==> v1beta1/PodDisruptionBudget
nginx-nginx-ingress-controller 2s
nginx-nginx-ingress-default-backend 2s
==> v1/ConfigMap
nginx-nginx-ingress-controller 2s
NOTES:
The nginx-ingress controller has been installed.
It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running 'kubectl --namespace default get services -o wide -w nginx-nginx-ingress-controller'
An example Ingress that makes use of the controller:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
name: example
namespace: foo
spec:
rules:
- host: www.example.com
http:
paths:
- backend:
serviceName: exampleService
servicePort: 80
path: /
# This section is only required if TLS is to be enabled for the Ingress
tls:
- hosts:
- www.example.com
secretName: example-tls
If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:
apiVersion: v1
kind: Secret
metadata:
name: example-tls
namespace: foo
data:
tls.crt: <base64 encoded cert>
tls.key: <base64 encoded key>
type: kubernetes.io/tls
kubectl get svc
output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.11.240.1 <none> 443/TCP 2h
nginx-nginx-ingress-controller LoadBalancer 10.11.251.27 35.192.109.155 80:31189/TCP,443:31618/TCP 1m
nginx-nginx-ingress-default-backend ClusterIP 10.11.251.62 <none> 80/TCP 1m
nano /etc/hosts
35.188.165.83 reddit reddit-prometheus reddit-grafana reddit-non-prod production reddit-kibana staging prod
git clone https://github.com/kubernetes/charts.git kube-charts
cd kube-charts ; git fetch origin pull/2767/head:prom_2.0
git checkout prom_2.0 ; cd ..
cp -r kube-charts/stable/prometheus <директория kubernetes/charts>
rm -r kube-charts
cd Charts/prometheus
rbac:
create: false
alertmanager:
## If false, alertmanager will not be installed
##
enabled: false
# Defines the serviceAccountName to use when `rbac.create=false`
serviceAccountName: default
## alertmanager container name
##
name: alertmanager
## alertmanager container image
##
image:
repository: prom/alertmanager
tag: v0.10.0
pullPolicy: IfNotPresent
## Additional alertmanager container arguments
##
extraArgs: {}
## The URL prefix at which the container can be accessed. Useful in the case the '-web.external-url' includes a slug
## so that the various internal URLs are still able to access as they are in the default case.
## (Optional)
baseURL: "/"
## Additional alertmanager container environment variable
## For instance to add a http_proxy
##
extraEnv: {}
## ConfigMap override where fullname is {{.Release.Name}}-{{.Values.alertmanager.configMapOverrideName}}
## Defining configMapOverrideName will cause templates/alertmanager-configmap.yaml
## to NOT generate a ConfigMap resource
##
configMapOverrideName: ""
ingress:
## If true, alertmanager Ingress will be created
##
enabled: false
## alertmanager Ingress annotations
##
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: 'true'
## alertmanager Ingress hostnames
## Must be provided if Ingress is enabled
##
hosts: []
# - alertmanager.domain.com
## alertmanager Ingress TLS configuration
## Secrets must be manually created in the namespace
##
tls: []
# - secretName: prometheus-alerts-tls
# hosts:
# - alertmanager.domain.com
## Alertmanager Deployment Strategy type
# strategy:
# type: Recreate
## Node labels for alertmanager pod assignment
## Ref: https://kubernetes.io/docs/user-guide/node-selection/
##
nodeSelector: {}
persistentVolume:
## If true, alertmanager will create/use a Persistent Volume Claim
## If false, use emptyDir
##
enabled: true
## alertmanager data Persistent Volume access modes
## Must match those of existing PV or dynamic provisioner
## Ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
##
accessModes:
- ReadWriteOnce
## alertmanager data Persistent Volume Claim annotations
##
annotations: {}
## alertmanager data Persistent Volume existing claim name
## Requires alertmanager.persistentVolume.enabled: true
## If defined, PVC must be created manually before volume will be bound
existingClaim: ""
## alertmanager data Persistent Volume mount root path
##
mountPath: /data
## alertmanager data Persistent Volume size
##
size: 2Gi
## alertmanager data Persistent Volume Storage Class
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# storageClass: "-"
## Subdirectory of alertmanager data Persistent Volume to mount
## Useful if the volume's root directory is not empty
##
subPath: ""
## Annotations to be added to alertmanager pods
##
podAnnotations: {}
replicaCount: 1
## alertmanager resource requests and limits
## Ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
resources: {}
# limits:
# cpu: 10m
# memory: 32Mi
# requests:
# cpu: 10m
# memory: 32Mi
service:
annotations: {}
labels: {}
clusterIP: ""
## List of IP addresses at which the alertmanager service is available
## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips
##
externalIPs: []
loadBalancerIP: ""
loadBalancerSourceRanges: []
servicePort: 80
# nodePort: 30000
type: ClusterIP
## Monitors ConfigMap changes and POSTs to a URL
## Ref: https://github.com/jimmidyson/configmap-reload
##
configmapReload:
## configmap-reload container name
##
name: configmap-reload
## configmap-reload container image
##
image:
repository: jimmidyson/configmap-reload
tag: v0.1
pullPolicy: IfNotPresent
## configmap-reload resource requests and limits
## Ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
resources: {}
kubeStateMetrics:
## If false, kube-state-metrics will not be installed
##
enabled: false
# Defines the serviceAccountName to use when `rbac.create=false`
serviceAccountName: default
## kube-state-metrics container name
##
name: kube-state-metrics
## kube-state-metrics container image
##
image:
repository: gcr.io/google_containers/kube-state-metrics
tag: v1.1.0
pullPolicy: IfNotPresent
## Node labels for kube-state-metrics pod assignment
## Ref: https://kubernetes.io/docs/user-guide/node-selection/
##
nodeSelector: {}
## Annotations to be added to kube-state-metrics pods
##
podAnnotations: {}
replicaCount: 1
## kube-state-metrics resource requests and limits
## Ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
resources: {}
# limits:
# cpu: 10m
# memory: 16Mi
# requests:
# cpu: 10m
# memory: 16Mi
service:
annotations:
prometheus.io/scrape: "true"
labels: {}
clusterIP: None
## List of IP addresses at which the kube-state-metrics service is available
## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips
##
externalIPs: []
loadBalancerIP: ""
loadBalancerSourceRanges: []
servicePort: 80
type: ClusterIP
nodeExporter:
## If false, node-exporter will not be installed
##
enabled: false
# Defines the serviceAccountName to use when `rbac.create=false`
serviceAccountName: default
## node-exporter container name
##
name: node-exporter
## node-exporter container image
##
image:
repository: prom/node-exporter
tag: v0.15.1
pullPolicy: IfNotPresent
## Custom Update Strategy
##
updateStrategy:
type: OnDelete
## Additional node-exporter container arguments
##
extraArgs: {}
## Additional node-exporter hostPath mounts
##
extraHostPathMounts: []
# - name: textfile-dir
# mountPath: /srv/txt_collector
# hostPath: /var/lib/node-exporter
# readOnly: true
## Node tolerations for node-exporter scheduling to nodes with taints
## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
##
tolerations: []
# - key: "key"
# operator: "Equal|Exists"
# value: "value"
# effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)"
## Node labels for node-exporter pod assignment
## Ref: https://kubernetes.io/docs/user-guide/node-selection/
##
nodeSelector: {}
## Annotations to be added to node-exporter pods
##
podAnnotations: {}
## node-exporter resource limits & requests
## Ref: https://kubernetes.io/docs/user-guide/compute-resources/
##
resources: {}
# limits:
# cpu: 200m
# memory: 50Mi
# requests:
# cpu: 100m
# memory: 30Mi
service:
annotations:
prometheus.io/scrape: "true"
labels: {}
clusterIP: None
## List of IP addresses at which the node-exporter service is available
## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips
##
externalIPs: []
hostPort: 9100
loadBalancerIP: ""
loadBalancerSourceRanges: []
servicePort: 9100
type: ClusterIP
server:
## Prometheus server container name
##
name: server
# Defines the serviceAccountName to use when `rbac.create=false`
serviceAccountName: default
## Prometheus server container image
##
image:
repository: prom/prometheus
tag: v2.0.0
pullPolicy: IfNotPresent
## (optional) alertmanager hostname
## only used if alertmanager.enabled = false
alertmanagerHostname: ""
## The URL prefix at which the container can be accessed. Useful in the case the '-web.external-url' includes a slug
## so that the various internal URLs are still able to access as they are in the default case.
## (Optional)
baseURL: ""
## Additional Prometheus server container arguments
##
extraArgs: {}
## Additional Prometheus server hostPath mounts
##
extraHostPathMounts: []
# - name: certs-dir
# mountPath: /etc/kubernetes/certs
# hostPath: /etc/kubernetes/certs
# readOnly: true
## ConfigMap override where fullname is {{.Release.Name}}-{{.Values.server.configMapOverrideName}}
## Defining configMapOverrideName will cause templates/server-configmap.yaml
## to NOT generate a ConfigMap resource
##
configMapOverrideName: ""
ingress:
## If true, Prometheus server Ingress will be created
##
enabled: true
## Prometheus server Ingress annotations
##
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: 'true'
## Prometheus server Ingress hostnames
## Must be provided if Ingress is enabled
##
hosts:
- reddit-prometheus
## Prometheus server Ingress TLS configuration
## Secrets must be manually created in the namespace
##
tls: []
# - secretName: prometheus-server-tls
# hosts:
# - prometheus.domain.com
## Server Deployment Strategy type
# strategy:
# type: Recreate
## Node tolerations for server scheduling to nodes with taints
## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
##
tolerations: []
# - key: "key"
# operator: "Equal|Exists"
# value: "value"
# effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)"
## Node labels for Prometheus server pod assignment
## Ref: https://kubernetes.io/docs/user-guide/node-selection/
nodeSelector: {}
persistentVolume:
## If true, Prometheus server will create/use a Persistent Volume Claim
## If false, use emptyDir
##
enabled: true
## Prometheus server data Persistent Volume access modes
## Must match those of existing PV or dynamic provisioner
## Ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
##
accessModes:
- ReadWriteOnce
## Prometheus server data Persistent Volume annotations
##
annotations: {}
## Prometheus server data Persistent Volume existing claim name
## Requires server.persistentVolume.enabled: true
## If defined, PVC must be created manually before volume will be bound
existingClaim: ""
## Prometheus server data Persistent Volume mount root path
##
mountPath: /data
## Prometheus server data Persistent Volume size
##
size: 8Gi
## Prometheus server data Persistent Volume Storage Class
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# storageClass: "-"
## Subdirectory of Prometheus server data Persistent Volume to mount
## Useful if the volume's root directory is not empty
##
subPath: ""
## Annotations to be added to Prometheus server pods
##
podAnnotations: {}
# iam.amazonaws.com/role: prometheus
replicaCount: 1
## Prometheus server resource requests and limits
## Ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
resources: {}
# limits:
# cpu: 500m
# memory: 512Mi
# requests:
# cpu: 500m
# memory: 512Mi
service:
annotations: {}
labels: {}
clusterIP: ""
## List of IP addresses at which the Prometheus server service is available
## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips
##
externalIPs: []
loadBalancerIP: ""
loadBalancerSourceRanges: []
servicePort: 80
type: LoadBalancer
## Prometheus server pod termination grace period
##
terminationGracePeriodSeconds: 300
## Prometheus data retention period (i.e 360h)
##
retention: ""
pushgateway:
## If false, pushgateway will not be installed
##
enabled: false
## pushgateway container name
##
name: pushgateway
## pushgateway container image
##
image:
repository: prom/pushgateway
tag: v0.4.0
pullPolicy: IfNotPresent
## Additional pushgateway container arguments
##
extraArgs: {}
ingress:
## If true, pushgateway Ingress will be created
##
enabled: false
## pushgateway Ingress annotations
##
annotations:
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: 'true'
## pushgateway Ingress hostnames
## Must be provided if Ingress is enabled
##
hosts: []
# - pushgateway.domain.com
## pushgateway Ingress TLS configuration
## Secrets must be manually created in the namespace
##
tls: []
# - secretName: prometheus-alerts-tls
# hosts:
# - pushgateway.domain.com
## Node labels for pushgateway pod assignment
## Ref: https://kubernetes.io/docs/user-guide/node-selection/
##
nodeSelector: {}
## Annotations to be added to pushgateway pods
##
podAnnotations: {}
replicaCount: 1
## pushgateway resource requests and limits
## Ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
resources: {}
# limits:
# cpu: 10m
# memory: 32Mi
# requests:
# cpu: 10m
# memory: 32Mi
service:
annotations:
prometheus.io/probe: pushgateway
labels: {}
clusterIP: ""
## List of IP addresses at which the pushgateway service is available
## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips
##
externalIPs: []
loadBalancerIP: ""
loadBalancerSourceRanges: []
servicePort: 9091
type: ClusterIP
## alertmanager ConfigMap entries
##
alertmanagerFiles:
alertmanager.yml: |-
global:
# slack_api_url: ''
receivers:
- name: default-receiver
# slack_configs:
# - channel: '@you'
# send_resolved: true
route:
group_wait: 10s
group_interval: 5m
receiver: default-receiver
repeat_interval: 3h
## Prometheus server ConfigMap entries
##
serverFiles:
alerts: {}
rules: {}
prometheus.yml:
rule_files:
- /etc/config/rules
- /etc/config/alerts
global:
scrape_interval: 30s
scrape_configs:
- job_name: prometheus
static_configs:
- targets:
- localhost:9090
# A scrape configuration for running Prometheus on a Kubernetes cluster.
# This uses separate scrape configs for cluster components (i.e. API server, node)
# and services to allow each to use different authentication configs.
#
# Kubernetes labels will be added as Prometheus labels on metrics via the
# `labelmap` relabeling action.
# Scrape config for API servers.
#
# Kubernetes exposes API servers as endpoints to the default/kubernetes
# service so this uses `endpoints` role and uses relabelling to only keep
# the endpoints associated with the default/kubernetes service using the
# default named port `https`. This works for single API server deployments as
# well as HA API server deployments.
- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
# Default to scraping over https. If required, just disable this or change to
# `http`.
scheme: https
# This TLS & bearer token file config is used to connect to the actual scrape
# endpoints for cluster components. This is separate to discovery auth
# configuration because discovery & scraping are two separate concerns in
# Prometheus. The discovery auth config is automatic if Prometheus runs inside
# the cluster. Otherwise, more config options have to be provided within the
# <kubernetes_sd_config>.
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# If your node certificates are self-signed or use a different CA to the
# master CA, then disable certificate verification below. Note that
# certificate verification is an integral part of a secure infrastructure
# so this should only be disabled in a controlled environment. You can
# disable certificate verification by uncommenting the line below.
#
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
# Keep only the default/kubernetes service endpoints for the https port. This
# will add targets for each API server which Kubernetes adds an endpoint to
# the default/kubernetes service.
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
- job_name: 'kubernetes-nodes'
# Default to scraping over https. If required, just disable this or change to
# `http`.
scheme: https
# This TLS & bearer token file config is used to connect to the actual scrape
# endpoints for cluster components. This is separate to discovery auth
# configuration because discovery & scraping are two separate concerns in
# Prometheus. The discovery auth config is automatic if Prometheus runs inside
# the cluster. Otherwise, more config options have to be provided within the
# <kubernetes_sd_config>.
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# If your node certificates are self-signed or use a different CA to the
# master CA, then disable certificate verification below. Note that
# certificate verification is an integral part of a secure infrastructure
# so this should only be disabled in a controlled environment. You can
# disable certificate verification by uncommenting the line below.
#
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
# Scrape config for service endpoints.
#
# The relabeling allows the actual service scrape endpoint to be configured
# via the following annotations:
#
# * `prometheus.io/scrape`: Only scrape services that have a value of `true`
# * `prometheus.io/scheme`: If the metrics endpoint is secured then you will need
# to set this to `https` & most likely set the `tls_config` of the scrape config.
# * `prometheus.io/path`: If the metrics path is not `/metrics` override this.
# * `prometheus.io/port`: If the metrics are exposed on a different port to the
# service then set this appropriately.
- job_name: 'kubernetes-service-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (https?)
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: (.+)(?::\d+);(\d+)
replacement: $1:$2
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_name
- job_name: 'prometheus-pushgateway'
honor_labels: true
kubernetes_sd_configs:
- role: service
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
action: keep
regex: pushgateway
# Example scrape config for probing services via the Blackbox Exporter.
#
# The relabeling allows the actual service scrape endpoint to be configured
# via the following annotations:
#
# * `prometheus.io/probe`: Only probe services that have a value of `true`
- job_name: 'kubernetes-services'
metrics_path: /probe
params:
module: [http_2xx]
kubernetes_sd_configs:
- role: service
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
action: keep
regex: true
- source_labels: [__address__]
target_label: __param_target
- target_label: __address__
replacement: blackbox
- source_labels: [__param_target]
target_label: instance
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
target_label: kubernetes_name
# Example scrape config for pods
#
# The relabeling allows the actual pod scrape endpoint to be configured via the
# following annotations:
#
# * `prometheus.io/scrape`: Only scrape pods that have a value of `true`
# * `prometheus.io/path`: If the metrics path is not `/metrics` override this.
# * `prometheus.io/port`: Scrape the pod on the indicated port instead of the default of `9102`.
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: (.+):(?:\d+);(\d+)
replacement: ${1}:${2}
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
networkPolicy:
## Enable creation of NetworkPolicy resources.
##
enabled: false
Основные отличия от values.yml:
• отключена часть устанавливаемых сервисов
(pushgateway, alertmanager, kube-state-metrics)
• включено создание Ingress’а для подключения через
nginx
• поправлен endpoint для сбора метрик cadvisor
• уменьшен интервал сбора метрик (с 1 минуты до 30
секунд)
helm upgrade prom . -f custom_values.yml --install
output
Release "prom" does not exist. Installing it now.
NAME: prom
LAST DEPLOYED: Mon Apr 30 00:17:31 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/Deployment
NAME AGE
prom-prometheus-server 3s
==> v1beta1/Ingress
prom-prometheus-server 3s
==> v1/ConfigMap
prom-prometheus-server 3s
==> v1/PersistentVolumeClaim
prom-prometheus-server 3s
==> v1/Service
prom-prometheus-server 3s
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prom-prometheus-server.default.svc.cluster.local
From outside the cluster, the server URL(s) are:
http://reddit-prometheus
For more information on running Prometheus, visit:
https://prometheus.io/
У нас уже присутствует ряд endpoint’ов для сбора метрик
• Метрики API-сервера
• метрики нод с cadvisor’ов
• сам prometheus
Отметим, что можно собирать метрики cadvisor’а (который уже является частью kubelet) через проксирующий запрос в kube-api-server.
Если зайти по ssh на любую из машин кластера и запросить
$ curl http://localhost:4194/metrics
то получим те же метрики у kubelet напрямую. Но вариант с kube-api предпочтительней, т.к. этот трафик шифруется TLS и требует аутентификации.
Таргеты для сбора метрик найдены с помощью service discovery (SD), настроенного в конфиге prometheus (лежит в custom_values.yml)
prometheus.yml:
...
- job_name: 'kubernetes-apiservers'
...
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs: # Настройки Service Discovery (для поиска target’ов)
- role: node
scheme: https # Настройки подключения к target’ам (для сбора метрик)
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs: # Настройки различных меток, фильтрация найденных таргетов, их изменение
Использование Service Discovery в kubernetes позволяет нам динамично менять кластер (как сами хосты, так и сервисы и приложения)
Цели для мониторинга находим c помощью запросов к k8s API:
scrape_configs:
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
Role:
объект, который нужно найти:
• node
• endpoints
• pod
• service
• ingress
scrape_configs:
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
Targets: • gke-cluster-1-default-pool-f9c66281-kxrc • gke-cluster-1-default-pool-f9c66281-8gkc • gke-cluster-1-big-pool-b4209075-jlnq
Т.к. сбор метрик prometheus осуществляется поверх стандартного HTTP-протокола, то могут понадобится доп.
настройки для безопасного доступа к метрикам.
Ниже приведены настройки для сбора метрик из k8s API.
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
1) Схема подключения - http (default) или https
2) Конфиг TLS - коревой сертификат сервера для проверки достоверности сервера
3) Токен для аутентификации на сервере
relabel_configs:
- action: labelmap # 1) преобразовать все k8s лейблы таргета в лейблы prometheus
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__ # 2) Поменять лейбл для адреса сбора метрик
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name] # 3) Поменять лейбл для пути сбора метрик
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
Все найденные на эндпоинтах метрики сразу же отобразятся в списке (вкладка Graph). Метрики Cadvisor начинаются с container_.
Cadvisor собирает лишь информацию о потреблении ресурсов ипроизводительности отдельных docker-контейнеров. При этом он ничего не знает о сущностях k8s (деплойменты, репликасеты, ...).
Для сбора этой информации будем использовать сервис kube-state-metrics. Он входит в чарт Prometheus. Включим его.
prometheus/custom_values.yml
kubeStateMetrics:
## If false, kube-state-metrics will not be installed
##
enabled: true
Обновим релиз
helm upgrade prom ./prometheus -f custom_values.yml --install
output
LAST DEPLOYED: Mon Apr 30 01:17:30 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME AGE
prom-prometheus-server 1h
==> v1/PersistentVolumeClaim
prom-prometheus-server 1h
==> v1/Service
prom-prometheus-kube-state-metrics 5s
prom-prometheus-server 1h
==> v1beta1/Deployment
prom-prometheus-kube-state-metrics 5s
prom-prometheus-server 1h
==> v1beta1/Ingress
prom-prometheus-server 1h
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prom-prometheus-server.default.svc.cluster.local
From outside the cluster, the server URL(s) are:
http://reddit-prometheus
For more information on running Prometheus, visit:
https://prometheus.io/
в Targets появился kubernetes-service-endpoints (1/1 up)
в Graph набираем kube и получаем огромный список метрик
По аналогии с kube_state_metrics включите (enabled: true) поды node-exporter в custom_values.yml. Проверьте, что метрики начали собираться с них.
nodeExporter:
## If false, node-exporter will not be installed
##
enabled: true
helm upgrade prom ./prometheus -f custom_values.yml --install
output
Release "prom" has been upgraded. Happy Helming!
LAST DEPLOYED: Mon Apr 30 01:26:25 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME AGE
prom-prometheus-server 1h
==> v1/PersistentVolumeClaim
prom-prometheus-server 1h
==> v1/Service
prom-prometheus-kube-state-metrics 8m
prom-prometheus-node-exporter 3s
prom-prometheus-server 1h
==> v1beta1/DaemonSet
prom-prometheus-node-exporter 3s
==> v1beta1/Deployment
prom-prometheus-kube-state-metrics 8m
prom-prometheus-server 1h
==> v1beta1/Ingress
prom-prometheus-server 1h
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prom-prometheus-server.default.svc.cluster.local
From outside the cluster, the server URL(s) are:
http://reddit-prometheus
For more information on running Prometheus, visit:
https://prometheus.io/
node_exporter_build_info
node_exporter_build_info{app="prometheus",branch="HEAD",chart="prometheus-5.0.0",component="node-exporter",goversion="go1.9.2",heritage="Tiller",instance="10.128.0.2:9100",job="kubernetes-service-endpoints",kubernetes_name="prom-prometheus-node-exporter",kubernetes_namespace="default",release="prom",revision="ba5da2c29ae7f6209a88cb58676ba5ba029ad785",version="0.15.1"}
helm upgrade reddit-test ./reddit --install
output
Release "reddit-test" does not exist. Installing it now.
NAME: reddit-test
LAST DEPLOYED: Mon Apr 30 01:30:45 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Secret
NAME AGE
reddit-test-mongodb 2s
==> v1/PersistentVolumeClaim
reddit-test-mongodb 2s
==> v1/Service
reddit-test-comment 2s
reddit-test-mongodb 2s
reddit-test-post 2s
reddit-test-ui 2s
==> v1beta2/Deployment
reddit-test-comment 2s
reddit-test-post 2s
==> v1beta1/Deployment
reddit-test-mongodb 2s
reddit-test-ui 2s
==> v1beta1/Ingress
reddit-test-ui 2s
helm upgrade production --namespace production ./reddit --install
output
Release "production" does not exist. Installing it now.
NAME: production
LAST DEPLOYED: Mon Apr 30 01:32:19 2018
NAMESPACE: production
STATUS: DEPLOYED
RESOURCES:
==> v1/Secret
NAME AGE
production-mongodb 2s
==> v1/PersistentVolumeClaim
production-mongodb 1s
==> v1/Service
production-comment 1s
production-mongodb 1s
production-post 1s
production-ui 1s
==> v1beta2/Deployment
production-comment 1s
production-post 1s
==> v1beta1/Deployment
production-mongodb 1s
production-ui 1s
==> v1beta1/Ingress
production-ui 1s
helm upgrade staging --namespace staging ./reddit --install
Release "staging" does not exist. Installing it now.
NAME: staging
LAST DEPLOYED: Mon Apr 30 01:33:42 2018
NAMESPACE: staging
STATUS: DEPLOYED
RESOURCES:
==> v1/Secret
NAME AGE
staging-mongodb 2s
==> v1/PersistentVolumeClaim
staging-mongodb 2s
==> v1/Service
staging-comment 2s
staging-mongodb 2s
staging-post 2s
staging-ui 2s
==> v1beta2/Deployment
staging-comment 2s
staging-post 2s
==> v1beta1/Deployment
staging-mongodb 2s
staging-ui 2s
==> v1beta1/Ingress
staging-ui 2s
Раньше мы “хардкодили” адреса/dns-имена наших приложений для сбора метрик с них. prometheus.yml
- job_name: 'ui'
static_configs:
- targets:
- 'ui:9292'
- job_name: 'comment'
static_configs:
- targets:
- 'comment:9292'
Теперь мы можем использовать механизм ServiceDiscovery для обнаружения приложений, запущенных в k8s.
- job_name: 'reddit-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_label_app]
action: keep
regex: reddit # Используем действие keep, чтобы оставить только эндпоинты сервисов с метками “app=reddit”
helm upgrade prom ./prometheus -f custom_values.yml --install
output
Release "prom" has been upgraded. Happy Helming!
LAST DEPLOYED: Mon Apr 30 01:44:55 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/Ingress
NAME AGE
prom-prometheus-server 1h
==> v1/ConfigMap
prom-prometheus-server 1h
==> v1/PersistentVolumeClaim
prom-prometheus-server 1h
==> v1/Service
prom-prometheus-kube-state-metrics 27m
prom-prometheus-node-exporter 18m
prom-prometheus-server 1h
==> v1beta1/DaemonSet
prom-prometheus-node-exporter 18m
==> v1beta1/Deployment
prom-prometheus-kube-state-metrics 27m
prom-prometheus-server 1h
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prom-prometheus-server.default.svc.cluster.local
From outside the cluster, the server URL(s) are:
http://reddit-prometheus
For more information on running Prometheus, visit:
https://prometheus.io/
Мы получили эндпоинты, но что это за поды мы не знаем.
__meta_kubernetes_service_label_labelname __meta_kubernetes_service_annotation_annotationname
custom_values.yml
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
Обновите релиз prometheus
helm upgrade prom ./prometheus -f custom_values.yml --install
output
Release "prom" has been upgraded. Happy Helming!
LAST DEPLOYED: Mon Apr 30 01:49:42 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/ConfigMap
NAME AGE
prom-prometheus-server 1h
==> v1/PersistentVolumeClaim
prom-prometheus-server 1h
==> v1/Service
prom-prometheus-kube-state-metrics 32m
prom-prometheus-node-exporter 23m
prom-prometheus-server 1h
==> v1beta1/DaemonSet
prom-prometheus-node-exporter 23m
==> v1beta1/Deployment
prom-prometheus-kube-state-metrics 32m
prom-prometheus-server 1h
==> v1beta1/Ingress
prom-prometheus-server 1h
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prom-prometheus-server.default.svc.cluster.local
From outside the cluster, the server URL(s) are:
http://reddit-prometheus
For more information on running Prometheus, visit:
https://prometheus.io/
Теперь мы видим лейблы k8s, присвоенные POD’ам
Добавим еще label’ы для prometheus и обновим helm-релиз Т.к. метки вида _meta* не публикуются, то нужно создать свои,перенеся в них информацию
- source_labels: [__meta_kubernetes_namespace] target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name] target_label: kubernetes_name
Обновим релиз prometheus
helm upgrade prom . -f custom_values.yml --install
Сейчас мы собираем метрики со всех сервисов reddit’а в 1 группеtarget-ов. Мы можем отделить target-ы компонент друг от друга (по окружениям, по самим компонентам), а также выключать и включать опцию мониторинга для них с помощью все тех же label-ов.
- job_name: 'reddit-production'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_service_label_app, __meta_kubernetes_namespace] # Для разных лейблов
action: keep
regex: reddit;(production|staging)+ # разные
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name] # регекспы
target_label: kubernetes_name
helm upgrade prom . -f custom_values.yml --install
Запустите приложение из helm чарта reddit
helm upgrade reddit-test ./reddit --install
helm upgrade production --namespace production ./reddit --install
helm upgrade staging --namespace staging ./reddit --install
- job_name: 'post-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_label_app]
action: keep
regex: post
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
target_label: kubernetes_name
- job_name: 'comment-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_label_app]
action: keep
regex: comment
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
target_label: kubernetes_name
- job_name: 'ui-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_label_app]
action: keep
regex: ui
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
target_label: kubernetes_name
helm upgrade --install grafana stable/grafana --set "adminPassword=admin" \
--set "service.type=NodePort" \
--set "ingress.enabled=true" \
--set "ingress.hosts={reddit-grafana}"
curl -X PUT -H "Content-Type: application/json" -d '{ "oldPassword": "9diAffhoMEWFaRasHRVBIdGk7Zp5LCMUyI5NES0Y", "newPassword": "admin", "confirmNew": "admin" }' http://35.188.165.83:3000/api/user/password --user admin:9diAffhoMEWFaRasHRVBIdGk7Zp5LCMUyI5NES0Y
output
Release "grafana" does not exist. Installing it now.
NAME: grafana
LAST DEPLOYED: Tue May 1 18:59:03 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Secret
NAME AGE
grafana 1s
==> v1/ConfigMap
grafana 1s
grafana-dashboards-json 1s
==> v1/Service
grafana 1s
==> v1beta2/Deployment
grafana 1s
NOTES:
1. Get your 'admin' user password by running:
kubectl get secret --namespace default grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:
grafana.default.svc.cluster.local
Get the Grafana URL to visit by running these commands in the same shell:
export POD_NAME=$(kubectl get pods --namespace default -l "app=grafana,component=" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace default port-forward $POD_NAME 3000
3. Login with the password from step 1 and the username: admin
#################################################################################
###### WARNING: Persistence is disabled!!! You will lose your data when #####
###### the Grafana pod is terminated. #####
#################################################################################
can't access the grafana dashboard. error: get http://admin:admin@localhost:3000/api/org
kubectl get logs
kubectl get svc
output
2m 6h 76 reddit-test-ui.152b6d4423206a14 Ingress Warning GCE loadbalancer-controller googleapi: Error 403: Quota 'BACKEND_SERVICES' exceeded. Limit: 5.0 globally., quotaExceeded
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana NodePort 10.11.246.135 <none> 80:31613/TCP 4m
kubernetes ClusterIP 10.11.240.1 <none> 443/TCP 23m
nginx-nginx-ingress-controller LoadBalancer 10.11.240.82 35.184.47.197 80:32435/TCP,443:30516/TCP 16m
nginx-nginx-ingress-default-backend ClusterIP 10.11.254.129 <none> 80/TCP 16m
prom-prometheus-kube-state-metrics ClusterIP None <none> 80/TCP 13m
prom-prometheus-node-exporter ClusterIP None <none> 9100/TCP 13m
prom-prometheus-server LoadBalancer 10.11.247.45 35.232.182.45 80:31276/TCP 13m
gcloud compute backend-services delete <backend-service name>
$ helm upgrade reddit-test ./reddit --install $ helm upgrade production --namespace production ./reddit --install $ helm upgrade staging --namespace staging ./reddit --install
kubectl label node gke-cluster-1-default-pool-672939b9-4hcd elastichost=true
Логирование в k8s будем выстраивать с помощью уже известного стека EFK:
• ElasticSearch - база данных + поисковый движок
• Fluentd - шипер (отправитель) и агрегатор логов
• Kibana - веб-интерфейс для запросов в хранилище и отображения их результатов
fluentd-ds.yaml
apiVersion: apps/v1beta2
kind: DaemonSet
metadata:
name: fluentd-es-v2.0.2
labels:
k8s-app: fluentd-es
version: v2.0.2
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
spec:
selector:
matchLabels:
k8s-app: fluentd-es
version: v2.0.2
template:
metadata:
labels:
k8s-app: fluentd-es
kubernetes.io/cluster-service: "true"
version: v2.0.2
# This annotation ensures that fluentd does not get evicted if the node
# supports critical pod annotation based priority scheme.
# Note that this does not guarantee admission on the nodes (#40573).
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ''
spec:
containers:
- name: fluentd-es
image: gcr.io/google-containers/fluentd-elasticsearch:v2.0.2
env:
- name: FLUENTD_ARGS
value: --no-supervisor -q
resources:
limits:
memory: 500Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: libsystemddir
mountPath: /host/lib
readOnly: true
- name: config-volume
mountPath: /etc/fluent/config.d
nodeSelector:
beta.kubernetes.io/fluentd-ds-ready: "true"
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
# It is needed to copy systemd library to decompress journals
- name: libsystemddir
hostPath:
path: /usr/lib64
- name: config-volume
configMap:
name: fluentd-es-config-v0.1.1
fluentd-configmap.yaml (ссылка на gist)
kind: ConfigMap
apiVersion: v1
data:
containers.input.conf: |-
# This configuration file for Fluentd / td-agent is used
# to watch changes to Docker log files. The kubelet creates symlinks that
# capture the pod name, namespace, container name & Docker container ID
# to the docker logs for pods in the /var/log/containers directory on the host.
# If running this fluentd configuration in a Docker container, the /var/log
# directory should be mounted in the container.
#
# These logs are then submitted to Elasticsearch which assumes the
# installation of the fluent-plugin-elasticsearch & the
# fluent-plugin-kubernetes_metadata_filter plugins.
# See https://github.com/uken/fluent-plugin-elasticsearch &
# https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter for
# more information about the plugins.
#
# Example
# =======
# A line in the Docker log file might look like this JSON:
#
# {"log":"2014/09/25 21:15:03 Got request with path wombat\n",
# "stream":"stderr",
# "time":"2014-09-25T21:15:03.499185026Z"}
#
# The time_format specification below makes sure we properly
# parse the time format produced by Docker. This will be
# submitted to Elasticsearch and should appear like:
# $ curl 'http://elasticsearch-logging:9200/_search?pretty'
# ...
# {
# "_index" : "logstash-2014.09.25",
# "_type" : "fluentd",
# "_id" : "VBrbor2QTuGpsQyTCdfzqA",
# "_score" : 1.0,
# "_source":{"log":"2014/09/25 22:45:50 Got request with path wombat\n",
# "stream":"stderr","tag":"docker.container.all",
# "@timestamp":"2014-09-25T22:45:50+00:00"}
# },
# ...
#
# The Kubernetes fluentd plugin is used to write the Kubernetes metadata to the log
# record & add labels to the log record if properly configured. This enables users
# to filter & search logs on any metadata.
# For example a Docker container's logs might be in the directory:
#
# /var/lib/docker/containers/997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b
#
# and in the file:
#
# 997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b-json.log
#
# where 997599971ee6... is the Docker ID of the running container.
# The Kubernetes kubelet makes a symbolic link to this file on the host machine
# in the /var/log/containers directory which includes the pod name and the Kubernetes
# container name:
#
# synthetic-logger-0.25lps-pod_default_synth-lgr-997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b.log
# ->
# /var/lib/docker/containers/997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b/997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b-json.log
#
# The /var/log directory on the host is mapped to the /var/log directory in the container
# running this instance of Fluentd and we end up collecting the file:
#
# /var/log/containers/synthetic-logger-0.25lps-pod_default_synth-lgr-997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b.log
#
# This results in the tag:
#
# var.log.containers.synthetic-logger-0.25lps-pod_default_synth-lgr-997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b.log
#
# The Kubernetes fluentd plugin is used to extract the namespace, pod name & container name
# which are added to the log message as a kubernetes field object & the Docker container ID
# is also added under the docker field object.
# The final tag is:
#
# kubernetes.var.log.containers.synthetic-logger-0.25lps-pod_default_synth-lgr-997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b.log
#
# And the final log record look like:
#
# {
# "log":"2014/09/25 21:15:03 Got request with path wombat\n",
# "stream":"stderr",
# "time":"2014-09-25T21:15:03.499185026Z",
# "kubernetes": {
# "namespace": "default",
# "pod_name": "synthetic-logger-0.25lps-pod",
# "container_name": "synth-lgr"
# },
# "docker": {
# "container_id": "997599971ee6366d4a5920d25b79286ad45ff37a74494f262e3bc98d909d0a7b"
# }
# }
#
# This makes it easier for users to search for logs by pod name or by
# the name of the Kubernetes container regardless of how many times the
# Kubernetes pod has been restarted (resulting in a several Docker container IDs).
# Json Log Example:
# {"log":"[info:2016-02-16T16:04:05.930-08:00] Some log text here\n","stream":"stdout","time":"2016-02-17T00:04:05.931087621Z"}
# CRI Log Example:
# 2016-02-17T00:04:05.931087621Z stdout F [info:2016-02-16T16:04:05.930-08:00] Some log text here
<source>
type tail
path /var/log/containers/*.log
pos_file /var/log/es-containers.log.pos
time_format %Y-%m-%dT%H:%M:%S.%NZ
tag kubernetes.*
read_from_head true
format multi_format
<pattern>
format json
time_key time
time_format %Y-%m-%dT%H:%M:%S.%NZ
</pattern>
<pattern>
format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
time_format %Y-%m-%dT%H:%M:%S.%N%:z
</pattern>
</source>
system.input.conf: |-
# Example:
# 2015-12-21 23:17:22,066 [salt.state ][INFO ] Completed state [net.ipv4.ip_forward] at time 23:17:22.066081
<source>
type tail
format /^(?<time>[^ ]* [^ ,]*)[^\[]*\[[^\]]*\]\[(?<severity>[^ \]]*) *\] (?<message>.*)$/
time_format %Y-%m-%d %H:%M:%S
path /var/log/salt/minion
pos_file /var/log/es-salt.pos
tag salt
</source>
# Example:
# Dec 21 23:17:22 gke-foo-1-1-4b5cbd14-node-4eoj startupscript: Finished running startup script /var/run/google.startup.script
<source>
type tail
format syslog
path /var/log/startupscript.log
pos_file /var/log/es-startupscript.log.pos
tag startupscript
</source>
# Examples:
# time="2016-02-04T06:51:03.053580605Z" level=info msg="GET /containers/json"
# time="2016-02-04T07:53:57.505612354Z" level=error msg="HTTP Error" err="No such image: -f" statusCode=404
<source>
type tail
format /^time="(?<time>[^)]*)" level=(?<severity>[^ ]*) msg="(?<message>[^"]*)"( err="(?<error>[^"]*)")?( statusCode=($<status_code>\d+))?/
path /var/log/docker.log
pos_file /var/log/es-docker.log.pos
tag docker
</source>
# Example:
# 2016/02/04 06:52:38 filePurge: successfully removed file /var/etcd/data/member/wal/00000000000006d0-00000000010a23d1.wal
<source>
type tail
# Not parsing this, because it doesn't have anything particularly useful to
# parse out of it (like severities).
format none
path /var/log/etcd.log
pos_file /var/log/es-etcd.log.pos
tag etcd
</source>
# Multi-line parsing is required for all the kube logs because very large log
# statements, such as those that include entire object bodies, get split into
# multiple lines by glog.
# Example:
# I0204 07:32:30.020537 3368 server.go:1048] POST /stats/container/: (13.972191ms) 200 [[Go-http-client/1.1] 10.244.1.3:40537]
<source>
type tail
format multiline
multiline_flush_interval 5s
format_firstline /^\w\d{4}/
format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
time_format %m%d %H:%M:%S.%N
path /var/log/kubelet.log
pos_file /var/log/es-kubelet.log.pos
tag kubelet
</source>
# Example:
# I1118 21:26:53.975789 6 proxier.go:1096] Port "nodePort for kube-system/default-http-backend:http" (:31429/tcp) was open before and is still needed
<source>
type tail
format multiline
multiline_flush_interval 5s
format_firstline /^\w\d{4}/
format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
time_format %m%d %H:%M:%S.%N
path /var/log/kube-proxy.log
pos_file /var/log/es-kube-proxy.log.pos
tag kube-proxy
</source>
# Example:
# I0204 07:00:19.604280 5 handlers.go:131] GET /api/v1/nodes: (1.624207ms) 200 [[kube-controller-manager/v1.1.3 (linux/amd64) kubernetes/6a81b50] 127.0.0.1:38266]
<source>
type tail
format multiline
multiline_flush_interval 5s
format_firstline /^\w\d{4}/
format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
time_format %m%d %H:%M:%S.%N
path /var/log/kube-apiserver.log
pos_file /var/log/es-kube-apiserver.log.pos
tag kube-apiserver
</source>
# Example:
# I0204 06:55:31.872680 5 servicecontroller.go:277] LB already exists and doesn't need update for service kube-system/kube-ui
<source>
type tail
format multiline
multiline_flush_interval 5s
format_firstline /^\w\d{4}/
format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
time_format %m%d %H:%M:%S.%N
path /var/log/kube-controller-manager.log
pos_file /var/log/es-kube-controller-manager.log.pos
tag kube-controller-manager
</source>
# Example:
# W0204 06:49:18.239674 7 reflector.go:245] pkg/scheduler/factory/factory.go:193: watch of *api.Service ended with: 401: The event in requested index is outdated and cleared (the requested history has been cleared [2578313/2577886]) [2579312]
<source>
type tail
format multiline
multiline_flush_interval 5s
format_firstline /^\w\d{4}/
format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
time_format %m%d %H:%M:%S.%N
path /var/log/kube-scheduler.log
pos_file /var/log/es-kube-scheduler.log.pos
tag kube-scheduler
</source>
# Example:
# I1104 10:36:20.242766 5 rescheduler.go:73] Running Rescheduler
<source>
type tail
format multiline
multiline_flush_interval 5s
format_firstline /^\w\d{4}/
format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
time_format %m%d %H:%M:%S.%N
path /var/log/rescheduler.log
pos_file /var/log/es-rescheduler.log.pos
tag rescheduler
</source>
# Example:
# I0603 15:31:05.793605 6 cluster_manager.go:230] Reading config from path /etc/gce.conf
<source>
type tail
format multiline
multiline_flush_interval 5s
format_firstline /^\w\d{4}/
format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
time_format %m%d %H:%M:%S.%N
path /var/log/glbc.log
pos_file /var/log/es-glbc.log.pos
tag glbc
</source>
# Example:
# I0603 15:31:05.793605 6 cluster_manager.go:230] Reading config from path /etc/gce.conf
<source>
type tail
format multiline
multiline_flush_interval 5s
format_firstline /^\w\d{4}/
format1 /^(?<severity>\w)(?<time>\d{4} [^\s]*)\s+(?<pid>\d+)\s+(?<source>[^ \]]+)\] (?<message>.*)/
time_format %m%d %H:%M:%S.%N
path /var/log/cluster-autoscaler.log
pos_file /var/log/es-cluster-autoscaler.log.pos
tag cluster-autoscaler
</source>
# Logs from systemd-journal for interesting services.
<source>
type systemd
filters [{ "_SYSTEMD_UNIT": "docker.service" }]
pos_file /var/log/gcp-journald-docker.pos
read_from_head true
tag docker
</source>
<source>
type systemd
filters [{ "_SYSTEMD_UNIT": "kubelet.service" }]
pos_file /var/log/gcp-journald-kubelet.pos
read_from_head true
tag kubelet
</source>
<source>
type systemd
filters [{ "_SYSTEMD_UNIT": "node-problem-detector.service" }]
pos_file /var/log/gcp-journald-node-problem-detector.pos
read_from_head true
tag node-problem-detector
</source>
forward.input.conf: |-
# Takes the messages sent over TCP
<source>
type forward
</source>
monitoring.conf: |-
# Prometheus Exporter Plugin
# input plugin that exports metrics
<source>
@type prometheus
</source>
<source>
@type monitor_agent
</source>
# input plugin that collects metrics from MonitorAgent
<source>
@type prometheus_monitor
<labels>
host ${hostname}
</labels>
</source>
# input plugin that collects metrics for output plugin
<source>
@type prometheus_output_monitor
<labels>
host ${hostname}
</labels>
</source>
# input plugin that collects metrics for in_tail plugin
<source>
@type prometheus_tail_monitor
<labels>
host ${hostname}
</labels>
</source>
output.conf: |-
# Enriches records with Kubernetes metadata
<filter kubernetes.**>
type kubernetes_metadata
</filter>
<match **>
type elasticsearch
log_level info
include_tag_key true
host elasticsearch-logging
port 9200
logstash_format true
logstash_prefix fluentd
# Set the chunk limits.
buffer_chunk_limit 2M
buffer_queue_limit 8
flush_interval 5s
# Never wait longer than 5 minutes between retries.
max_retry_wait 30
# Disable the limit on the number of retries (retry forever).
disable_retry_limit
# Use multiple threads for processing.
num_threads 2
</match>
metadata:
name: fluentd-es-config-v0.1.1
labels:
addonmanager.kubernetes.io/mode: Reconcile
es-service.yaml
apiVersion: v1
kind: Service
metadata:
name: elasticsearch-logging
labels:
k8s-app: elasticsearch-logging
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
kubernetes.io/name: "Elasticsearch"
spec:
ports:
- port: 9200
protocol: TCP
targetPort: db
selector:
k8s-app: elasticsearch-logging
es-statefulSet.yaml
apiVersion: apps/v1beta2
kind: StatefulSet
metadata:
name: elasticsearch-logging
labels:
k8s-app: elasticsearch-logging
version: v5.6.4
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
spec:
serviceName: elasticsearch-logging
replicas: 1
selector:
matchLabels:
k8s-app: elasticsearch-logging
version: v5.6.4
template:
metadata:
labels:
k8s-app: elasticsearch-logging
version: v5.6.4
kubernetes.io/cluster-service: "true"
spec:
nodeSelector:
elastichost: "true"
containers:
- image: k8s.gcr.io/elasticsearch:v5.6.4
name: elasticsearch-logging
resources:
# need more cpu upon initialization, therefore burstable class
limits:
cpu: 1000m
requests:
cpu: 100m
ports:
- containerPort: 9200
name: db
protocol: TCP
- containerPort: 9300
name: transport
protocol: TCP
volumeMounts:
- name: es-pvc-volume
mountPath: /data
env:
- name: MINIMUM_MASTER_NODES
value: "1"
- name: "NAMESPACE"
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumes:
- name: es-pvc-volume
persistentVolumeClaim:
claimName: elasticsearch-logging-claim
# Elasticsearch requires vm.max_map_count to be at least 262144.
# If your OS already sets up this number to a higher value, feel free
# to remove this init container.
initContainers:
- image: alpine:3.6
command: ["/sbin/sysctl", "-w", "vm.max_map_count=262144"]
name: elasticsearch-logging-init
securityContext:
privileged: true
es-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: elasticsearch-logging-claim
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
kubectl apply -f ./efk
helm upgrade --install kibana stable/kibana \
--set "ingress.enabled=true" \
--set "ingress.hosts={reddit-kibana}" \
--set "env.ELASTICSEARCH_URL=http://elasticsearch-logging:9200" \
--version 0.1.1
kubernetes.labels.component:post OR kubernetes.labels.component:comment OR kubernetes.labels.component:ui
1) Особенность работы fluentd в k8s состоит в том, что его задача
помимо сбора самих логов приложений, сервисов и хостов, также
распознать дополнительные метаданные (как правило это
дополнительные поля с лейблами)
2) Откуда и какие логи собирает fluentd - видно в его fluentd-
configmap.yaml и в fluentd-ds.yaml
Helm - пакетный менеджер для Kubernetes.
С его помощью мы будем:
1) Стандартизировать поставку приложения в Kubernetes
2) Декларировать инфраструктуру
3) Деплоить новые версии приложения
Helm - клиент-серверное приложение.
https://github.com/kubernetes/helm/releases
- распаковываем и размещаем исполняемый файл helm в директории исполнения (/usr/local/bin/ , /usr/bin, ...)
Helm читает конфигурацию kubectl ( ~/.kube/config ) и сам
определяет текущий контекст (кластер, пользователь,
неймспейс)
Если хотите сменить кластер, то либо меняйте контекст с
помощью
$ kubectl config set-context
либо подгружайте helm’у собственный config-файл
флагом --kube-context .
Tiller - это аддон Kubernetes, т.е. Pod, который общается с API Kubernetes.
Для этого понадобится ему выдать ServiceAccount и назначить роли RBAC, необходимые для работы.
apiVersion: v1
kind: ServiceAccount
metadata:
name: tiller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: tiller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: tiller
namespace: kube-system
kubectl apply -f tiller.yml
helm init --service-account tiller
output
Creating /home/asomir/.helm
Creating /home/asomir/.helm/repository
Creating /home/asomir/.helm/repository/cache
Creating /home/asomir/.helm/repository/local
Creating /home/asomir/.helm/plugins
Creating /home/asomir/.helm/starters
Creating /home/asomir/.helm/cache/archive
Creating /home/asomir/.helm/repository/repositories.yaml
Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com
Adding local repo with URL: http://127.0.0.1:8879/charts
$HELM_HOME has been configured at /home/asomir/.helm.
Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.
Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!
kubectl get pods -n kube-system --selector app=helm
NAME READY STATUS RESTARTS AGE
tiller-deploy-56f4644cbb-dhj9b 1/1 Running 0 1m
Chart - это пакет в Helm.
Создаём директорию Charts в папке kubernetes со следующей структурой директорий:
mkdir comment post reddit ui
ui/Chart.yaml helm предпочитает .yaml
name: ui # Реально значимыми являются поля name и version.
version: 1.0.0 # От них зависит работа Helm’а с Chart’ом. Остальное - описания.
description: OTUS reddit application UI
maintainers:
- name: Alexander Akilin
email: asomirl@gmail.com
appVersion: 1.0
Основным содержимым Chart’ов являются шаблоны манифестов Kubernetes.
1) Создаём директорию ui/templates
2) Переносим в неё все манифесты, разработанные ранее для сервиса ui (ui-service, ui-deployment, ui-ingress)
3) Переименуем их (уберите префикс “ui-“) и поменяем расширение на .yaml) - стилистические правки
mv ui* Charts/ui/templates/
rename 's/yml/yaml/' *.yml
rename 's/ui-//' ui-*
cd ..
cd ..
tree
output
└── ui
├── Chart.yaml
└── templates
├── deployment.yaml
├── ingress.yaml
└── service.yaml
По-сути, это уже готовый пакет для установки в Kubernetes
kubectl delete deploy ui -n dev
kubectl delete service ui -n dev
kubectl delete ingress ui
helm install --name test-ui-1 ui/
output
NAME: test-ui-1
LAST DEPLOYED: Sun Apr 15 16:12:41 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/Deployment
NAME AGE
ui 1s
==> v1beta1/Ingress
ui 1s
==> v1/Service
ui 1s
helm ls
NAME REVISION UPDATED STATUS CHART NAMESPACE
test-ui-1 1 Sun Apr 15 16:12:41 2018 DEPLOYED ui-1.0.0 default
Теперь сделаем так, чтобы можно было использовать 1 Chart для запуска нескольких экземпляров (релизов). Шаблонизируем его.
ui/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }} # Нам нужно уникальное имя запущенного ресурса
labels:
app: reddit
component: ui
release: {{ .Release.Name }} # Помечаем, что сервис из конкретного релиза
spec:
type: NodePort
ports:
- port: 9292
protocol: TCP
targetPort: 9292
selector:
app: reddit
component: ui
release: {{ .Release.Name }} # Выбираем поды только из этого релиза
name: {{ .Release.Name }}-{{ .Chart.Name }}
Здесь мы используем встроенные переменные
.Release - группа переменных с информацией о релизе (конкретном запуске Chart’а в k8s)
.Chart - группа переменных с информацией о Chart’е (содержимое файла Chart.yaml)
Также еще есть группы переменных:
.Template - информация о текущем шаблоне ( .Name и .BasePath)
.Capabilities - информация о Kubernetes (версия, версии API)
.Files.Get - получить содержимое файла
ui/templates/deployment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: reddit
component: ui
release: {{ .Release.Name }}
spec:
replicas: 3
strategy:
type: Recreate
selector: # важно, чтобы selector deployment’а нашел только нужные POD’ы.
matchLabels:
app: reddit
component: ui
release: {{ .Release.Name }}
template:
metadata:
name: ui-pod
labels:
app: reddit
component: ui
release: {{ .Release.Name }}
spec:
containers:
- image: asomir/ui
name: ui
ports:
- containerPort: 9292
name: ui
protocol: TCP
env:
- name: ENV
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ui/templates/ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
annotations:
kubernetes.io/ingress.class: "gce"
spec:
rules:
- http:
paths:
- path: /*
backend:
serviceName: {{ .Release.Name }}-{{ .Chart.Name }}
servicePort: 9292
helm install ui --name ui-1
helm install ui --name ui-2
helm install ui --name ui-3
ui-1 ui-2 ui-3 - Имя релиза
output
NAME: ui-3
LAST DEPLOYED: Sun Apr 15 16:25:47 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Service
NAME AGE
ui-3-ui 0s
==> v1beta1/Deployment
ui-3-ui 0s
==> v1beta1/Ingress
ui-3-ui 0s
kubectl get ingress
output
NAME HOSTS ADDRESS PORTS AGE
ui-1-ui * 35.186.251.110 80 2m
ui-2-ui * 35.190.51.208 80 2m
ui-3-ui * 35.190.44.156 80 2m
По IP-адресам можно попасть на разные релизы ui- приложений (необходимо подождать несколько минут).
Мы уже сделали возможность запуска нескольких версий приложений из одного пакета манифестов, используя лишь встроенные переменные.
ui/templates/deployment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: reddit
component: ui
release: {{ .Release.Name }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: reddit
component: ui
release: {{ .Release.Name }}
template:
metadata:
name: ui
labels:
app: reddit
component: ui
release: {{ .Release.Name }}
spec:
containers:
- image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" # Ссылка на наш репозиторий и сам имедж
name: ui
ports:
- containerPort: {{ .Values.service.internalPort }} # Параметризируем порт
name: ui
protocol: TCP
env:
- name: ENV
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ui/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: reddit
component: ui
release: {{ .Release.Name }}
spec:
type: NodePort
ports:
- port: {{ .Values.service.externalPort }} # Параметризируем порт внешний
protocol: TCP
targetPort: {{ .Values.service.internalPort }} # И внутренний
selector:
app: reddit
component: ui
release: {{ .Release.Name }}
ui/templates/ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
annotations:
kubernetes.io/ingress.class: "gce"
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: {{ .Release.Name }}-{{ .Chart.Name }}
servicePort: {{ .Values.service.externalPort }}
service:
internalPort: 9292
externalPort: 9292
image:
repository: asomir/ui
tag: latest
helm upgrade ui-1 ui/
helm install ui-2 ui/
helm install ui-3 ui/
└── ui
├── Chart.yaml
└── templates
├── deployment.yaml
├── ingress.yaml
└── service.yaml
post/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: reddit
component: post
release: {{ .Release.Name }}
spec:
type: ClusterIP
ports:
- port: {{ .Values.service.externalPort }}
protocol: TCP
targetPort: {{ .Values.service.internalPort }}
selector:
app: reddit
component: post
release: {{ .Release.Name }}
post/templates/deployment.yaml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: reddit
component: post
release: {{ .Release.Name }}
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: post
release: {{ .Release.Name }}
template:
metadata:
name: post
labels:
app: reddit
component: post
release: {{ .Release.Name }}
spec:
containers:
- image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
name: post
ports:
- containerPort: {{ .Values.service.internalPort }}
name: post
protocol: TCP
env:
- name: POST_DATABASE_HOST
value: postdb
Поскольку адрес БД может меняться в зависимости от условий запуска:
• бд отдельно от кластера
• бд запущено в отдельном релизе
• ...
, то создадим удобный шаблон для задания адреса БД.
env:
- name: POST_DATABASE_HOST
value: {{ .Values.databaseHost }}
Будем задавать бд через переменную databaseHost. Иногда лучше использовать подобный формат переменных
вместо структур database.host, так как тогда прийдется определять структуру database, иначе helm выдаст ошибку.
Используем функцию default. Если databaseHost не будет определена или ее значение будет пустым, то используется
вывод функции printf (которая просто формирует строку <имя- релиза>-mongodb)
value: {{ .Values.databaseHost | default (printf "%s-mongodb" .Release.Name) }}
|
release-name-mongodb
В итоге должно получиться
env:
- name: POST_DATABASE_HOST
value: {{ .Values.databaseHost | default (printf "%s-mongodb" .Release.Name) }}
Теперь, если databaseHost не задано, то будет использован адрес базы, поднятой внутри релиза
post/values.yaml
service:
internalPort: 5000
externalPort: 5000
image:
repository: asomir/post
tag: latest
databaseHost:
comment/templates/deployment.yaml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: reddit
component: comment
release: {{ .Release.Name }}
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: comment
release: {{ .Release.Name }}
template:
metadata:
name: comment
labels:
app: reddit
component: comment
release: {{ .Release.Name }}
spec:
containers:
- image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
name: comment
ports:
- containerPort: {{ .Values.service.internalPort }}
name: comment
protocol: TCP
env:
- name: COMMENT_DATABASE_HOST
value: {{ .Values.databaseHost | default (printf "%s-mongodb" .Release.Name) }}
comment/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: reddit
component: comment
release: {{ .Release.Name }}
spec:
type: ClusterIP
ports:
- port: {{ .Values.service.externalPort }}
protocol: TCP
targetPort: {{ .Values.service.internalPort }}
selector:
app: reddit
component: comment
release: {{ .Release.Name }}
comment/values.yaml
service:
internalPort: 9292
externalPort: 9292
image:
repository: asomir/comment
tag: latest
databaseHost:
├── comment
│ ├── Chart.yaml
│ ├── templates
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ └── values.yaml
├── post
│ ├── Chart.yaml
│ ├── templates
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ └── values.yaml
├── reddit
└── ui
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── ingress.yaml
│ └── service.yaml
└── values.yaml
Также стоит отметить функционал helm по использованию helper’ов и функции templates.
Helper - это написанная нами функция. В функция описывается, как правило, сложная логика.
Шаблоны этих функций распологаются в файле _helpers.tpl
charts/comment/templates/_helpers.tpl
{{- define "comment.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name }}
{{- end -}}
которая в результате выдаст то же, что и: {{ .Release.Name }}-{{ .Chart.Name }}
charts/comment/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ template "comment.fullname" . }} # было name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: reddit
component: comment
release: {{ .Release.Name }}
spec:
type: ClusterIP
ports:
- port: {{ .Values.service.externalPort }}
protocol: TCP
targetPort: {{ .Values.service.internalPort }}
selector:
app: reddit
component: comment
release: {{ .Release.Name }}
с помощью template вызывается функция comment.fullname, описанная ранее в файле _helpers.tpl
{{ template "comment.fullname" . }}
| | |
Функция template Название область видимости
функции для для импорта
импорта
“.”- вся область видимости всех переменных (можно передать .Chart , тогда .Values не будут доступны внутри функции)
-
Создать файл _helpers.tpl в папках templates сервисов ui, post и comment
-
вставить функцию “.fullname” в каждый _helpers.tpl файл. заменить на имя чарта соотв. сервиса {{- define "post.fullname" -}} {{- define "comment.fullname" -}} {{- define "ui.fullname" -}}
-
В каждом из шаблонов манифестов вставить следующую функцию там, где это требуется (большинство полей это name: ) name: {{ template "comment.fullname" . }} name: {{ template "ui.fullname" . }} name: {{ template "post.fullname" . }}
├── comment
│ ├── Chart.yaml
│ ├── templates
│ │ ├── deployment.yaml
│ │ ├── _helpers.tpl
│ │ └── service.yaml
│ └── values.yaml
├── post
│ ├── Chart.yaml
│ ├── templates
│ │ ├── deployment.yaml
│ │ ├── _helpers.tpl
│ │ └── service.yaml
│ └── values.yaml
├── reddit
└── ui
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── ingress.yaml
│ └── service.yaml
└── values.yaml
Мы создали Chart’ы для каждой компоненты нашего приложения. Каждый из них можно запустить по-отдельности командой
helm install <chart-path> <release-name>
Но они будут запускаться в разных релизах, и не будут видеть друг друга. С помощью механизма управления зависимостями создадим единый Chart reddit, который объединит наши компоненты
reddit
/ | | \
UI Mongo Post Comment
reddit/Chart.yaml
name: reddit
version: 0.1.0
description: OTUS sample reddit application UI
maintainers:
- name: Alexander Akilin
email: asomirl@gmail.com
reddit/values.yaml
dependencies:
- name: ui # Имя и версия должны совпадать с содеражанием ui/Chart.yml
version: "1.0.0"
repository: "file://../ui" # Путь относительно расположения самого requiremetns.yml
- name: post
version: 1.0.0
repository: file://../post
- name: comment
version: 1.0.0
repository: file://../comment
helm dep update
output
Hang tight while we grab the latest from your chart repositories...
...Unable to get an update from the "local" chart repository (http://127.0.0.1:8879/charts):
Get http://127.0.0.1:8879/charts/index.yaml: dial tcp 127.0.0.1:8879: connect: connection refused
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 3 charts
Deleting outdated charts
МАГИЯ:
1) Появился файл requirements.lock с фиксацией зависимостей
2) Была создана директория charts с зависимостями в виде архивов
.
├── charts
│ ├── comment-1.0.0.tgz
│ ├── post-1.0.0.tgz
│ └── ui-1.0.0.tgz
├── Chart.yaml
├── requirements.lock
├── requirements.yaml
└── values.yaml
helm search mongo
output
NAME CHART VERSION APP VERSION DESCRIPTION
stable/mongodb 2.0.2 3.7.3 NoSQL document-oriented database that stores JS...
stable/mongodb-replicaset 3.3.0 3.6 NoSQL document-oriented database that stores JS...
dependencies:
- name: ui
version: "1.0.0"
repository: "file://../ui"
- name: post
version: 1.0.0
repository: file://../post
- name: comment
version: 1.0.0
repository: file://../comment
- name: mongodb
version: 0.4.18
repository: https://kubernetes-charts.storage.googleapis.com
helm dep update
helm install reddit --name reddit-test
helm ls --all
output
NAME REVISION UPDATED STATUS CHART NAMESPACE
reddit-test 1 Mon Apr 23 14:55:11 2018 DEPLOYED reddit-0.1.0 default
test-ui-1 1 Sat Apr 21 22:57:01 2018 DEPLOYED ui-1.0.0 default
ui-1 1 Sat Apr 21 22:57:50 2018 DEPLOYED ui-1.0.0 default
ui-2 1 Sat Apr 21 22:58:02 2018 DEPLOYED ui-1.0.0 default
ui-3 1 Sat Apr 21 22:58:13 2018 DEPLOYED ui-1.0.0 default
kubectl get ingress
Есть проблема с тем, что UI-сервис не знает как правильно ходить в post и comment сервисы. Ведь их имена теперь динамические и зависят от имен чартов В Dockerfile UI-сервиса уже заданы переменные окружения. Надо, чтобы они указывали на нужные бекенды
ENV POST_SERVICE_HOST post ENV POST_SERVICE_PORT 5000 ENV COMMENT_SERVICE_HOST comment ENV COMMENT_SERVICE_PORT 9292
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: {{ template "ui.fullname" . }}
labels:
app: reddit
component: ui
release: {{ .Release.Name }}
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: ui
release: {{ .Release.Name }}
template:
metadata:
name: ui-pod
labels:
app: reddit
component: ui
release: {{ .Release.Name }}
spec:
containers:
- image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
name: ui
ports:
- containerPort: {{ .Values.service.internalPort }}
name: ui
protocol: TCP
env:
- name: POST_SERVICE_HOST
value: {{ .Values.postHost | default (printf "%s-post" .Release.Name) }}
- name: POST_SERVICE_PORT
value: {{ .Values.postPort | default "5000" | quote }}
- name: COMMENT_SERVICE_HOST
value: {{ .Values.commentHost | default (printf "%s-comment" .Release.Name) }}
- name: COMMENT_SERVICE_PORT
value: {{ .Values.commentPort | default "9292" | quote }} # quote - функция для добавления кавычек Для чисел и булевых значений это важно
- name: ENV
valueFrom:
fieldRef:
fieldPath: metadata.namespace
postHost:
postPort:
commentHost:
commentPort:
Они перезаписывают значения переменных из зависимых чартов
reddit/values.yaml
comment: # ссылаемся на переменные чартов из зависимостей
image:
repository: asomir/comment
tag: latest
service:
externalPort: 9292
post: # ссылаемся на переменные чартов из зависимостей
image:
repository: asomir/post
tag: latest
service:
externalPort: 5000
ui: # ссылаемся на переменные чартов из зависимостей
image:
repository: asomir/ui
tag: latest
service:
externalPort: 9292
helm dep update ./reddit
output
Hang tight while we grab the latest from your chart repositories...
...Unable to get an update from the "local" chart repository (http://127.0.0.1:8879/charts):
Get http://127.0.0.1:8879/charts/index.yaml: dial tcp 127.0.0.1:8879: connect: connection refused
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 4 charts
Downloading mongodb from repo https://kubernetes-charts.storage.googleapis.com
Deleting outdated charts
$ helm upgrade ./reddit
helm upgrade reddit-test ./reddit
output
Release "reddit-test" has been upgraded. Happy Helming!
LAST DEPLOYED: Mon Apr 23 15:15:07 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Secret
NAME AGE
reddit-test-mongodb 19m
==> v1/PersistentVolumeClaim
reddit-test-mongodb 19m
==> v1/Service
reddit-test-comment 19m
reddit-test-mongodb 19m
reddit-test-post 19m
reddit-test-ui 19m
==> v1beta2/Deployment
reddit-test-comment 19m
reddit-test-post 19m
==> v1beta1/Deployment
reddit-test-mongodb 19m
reddit-test-ui 19m
==> v1beta1/Ingress
reddit-test-ui 19m
Проверим, всё ли поднялось. (Сначала поднялось далеков не всё, пришлось убивать и заново настраивать кластер, потому что ругалось на кол-во ядер)
Удалили для чистоты эксперимента кластер и заново пересоздали через GCP. Подключаемся к созданному кластеру
gcloud container clusters get-credentials cluster-1 --zone us-central1-a --project docker-194414
kubectl scale deployment --replicas 3 -n kube-system kube-dns-autoscaler
gcloud beta container clusters update cluster-1 --zone=us-central1-a --enable-network-policy
gcloud beta container clusters update cluster-1 --zone=us-central1-a --update-addons=NetworkPolicy=ENABLED
kubectl apply -f tiller.yml
helm init --service-account tiller
cd Charts/reddit
helm install reddit --name reddit-test
kubectl get pods
NAME READY STATUS RESTARTS AGE
reddit-test-comment-8675dbb45f-hkph2 1/1 Running 0 10h
reddit-test-mongodb-f555f9bdb-hwt9x 1/1 Running 0 10h
reddit-test-post-6f874c89cd-42ttm 1/1 Running 0 10h
reddit-test-post-6f874c89cd-4w9x8 1/1 Running 0 10h
reddit-test-post-6f874c89cd-p2rl7 1/1 Running 0 10h
reddit-test-ui-9b5b4898-gjjtf 1/1 Running 0 10h
kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
reddit-test-ui * 35.190.94.49 80 10h
Проверяем http://35.190.94.49/
Microservices Reddit in default reddit-test-ui-9b5b4898-gjjtf container
helm repo add gitlab https://charts.gitlab.io
helm fetch gitlab/gitlab-omnibus --version 0.1.36 --untar
cd gitlab-omnibus
helm install --name gitlab . -f values.yaml
Выпадает ошибка
Error: Get http://localhost:8080/api/v1/namespaces/kube-system/configmaps?labelSelector=OWNER%!D(MISSING)TILLER: dial tcp 127.0.0.1:8080: connect: connection refused
kubectl --namespace=kube-system edit deployment/tiller-deploy
Error: Chart incompatible with Tiller v2.9.0-rc3
apiVersion: v1
description: GitLab Omnibus all-in-one bundle
home: https://about.gitlab.com
icon: https://gitlab.com/gitlab-com/gitlab-artwork/raw/master/logo/logo-square.png
keywords:
- git
- ci
- cd
- deploy
- issue tracker
- code review
- wiki
maintainers:
- email: support@gitlab.com
name: GitLab Inc.
- name: Mark Pundsack
- name: Jason Plum
- name: DJ Mountney
- name: Joshua Lambert
name: gitlab-omnibus
sources:
- http://docs.gitlab.com/ce/install/kubernetes/
- https://gitlab.com/charts/charts.gitlab.io
version: 0.1.37
helm install --name gitlab . -f values.yaml
output
NAME: gitlab
LAST DEPLOYED: Fri Apr 27 15:23:08 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/Ingress
NAME AGE
gitlab-gitlab 2s
==> v1/ConfigMap
gitlab-gitlab-runner 2s
gitlab-gitlab-config 2s
gitlab-gitlab-postgresql-initdb 2s
kube-lego 2s
nginx 2s
tcp-ports 2s
==> v1/StorageClass
gitlab-gitlab-fast 2s
==> v1/PersistentVolumeClaim
gitlab-gitlab-config-storage 2s
gitlab-gitlab-registry-storage 2s
gitlab-gitlab-storage 2s
gitlab-gitlab-postgresql-storage 2s
gitlab-gitlab-redis-storage 2s
==> v1/Service
gitlab-gitlab 2s
gitlab-gitlab-postgresql 2s
gitlab-gitlab-redis 2s
default-http-backend 2s
nginx 2s
==> v1beta1/DaemonSet
nginx 2s
==> v1/Namespace
kube-lego 2s
nginx-ingress 2s
==> v1/Secret
gitlab-gitlab-runner 2s
gitlab-gitlab-secrets 2s
==> v1beta1/Deployment
gitlab-gitlab-runner 2s
gitlab-gitlab 2s
gitlab-gitlab-postgresql 2s
gitlab-gitlab-redis 2s
kube-lego 2s
default-http-backend 2s
NOTES:
It may take several minutes for GitLab to reconfigure.
You can watch the status by running `kubectl get deployment -w gitlab-gitlab --namespace default
You did not specify a baseIP so one will be assigned for you.
It may take a few minutes for the LoadBalancer IP to be available.
Watch the status with: 'kubectl get svc -w --namespace nginx-ingress nginx', then:
export SERVICE_IP=$(kubectl get svc --namespace nginx-ingress nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
Then make sure to configure DNS with something like:
*.example.com 300 IN A $SERVICE_IP
kubectl get service -n nginx-ingress nginx
output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.11.255.184 35.224.52.250 80:30339/TCP,443:30267/TCP,22:32649/TCP 11m
echo "35.224.52.250 gitlab-gitlab staging production” >> /etc/hosts
kubectl get pods
output
NAME READY STATUS RESTARTS AGE
gitlab-gitlab-764bd7665-8pr5z 1/1 Running 0 18m
gitlab-gitlab-postgresql-5fff4f67bb-hp8cb 1/1 Running 0 18m
gitlab-gitlab-redis-6c88945d56-56xzd 1/1 Running 0 18m
gitlab-gitlab-runner-f7c85548-q9z45 1/1 Running 4 18m
Идем по адресу http://gitlab-gitlab
1) Ставим собственный пароль.
2) Логинимся под пользователем root и новым паролем otusgitlab
Создаем группу В качестве имени свой Docker ID asomir. http://gitlab-gitlab/asomir В настройках группы выберем пункт CI/CD
Добавляем 2 переменные - Эти учетные данные будут использованы при сборке и релизе docker-образов с помощью Gitlab CI
CI_REGISTRY_USER - логин в dockerhub CI_REGISTRY_PASSWORD - пароль от Docker Hub
├── comment
│ ├── build_info.txt
│ ├── comment_app.rb
│ ├── config.ru
│ ├── docker_build.sh
│ ├── Dockerfile
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── helpers.rb
│ └── VERSION
├── post
│ ├── build_info.txt
│ ├── docker_build.sh
│ ├── Dockerfile
│ ├── get-pip.py
│ ├── helpers.py
│ ├── post_app.py
│ ├── requirements.txt
│ └── VERSION
├── reddit-deploy
│ ├── comment
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ │ ├── deployment.yaml
│ │ │ ├── _helpers.tpl
│ │ │ └── service.yaml
│ │ └── values.yaml
│ ├── post
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ │ ├── deployment.yaml
│ │ │ ├── _helpers.tpl
│ │ │ └── service.yaml
│ │ └── values.yaml
│ ├── reddit
│ │ ├── charts
│ │ │ ├── comment-1.0.0.tgz
│ │ │ ├── mongodb-0.4.18.tgz
│ │ │ ├── post-1.0.0.tgz
│ │ │ └── ui-1.0.0.tgz
│ │ ├── Chart.yaml
│ │ ├── requirements.lock
│ │ ├── requirements.yaml
│ │ ├── requirements.yml
│ │ └── values.yaml
│ └── ui
│ ├── Chart.yaml
│ ├── templates
│ │ ├── deployment.yaml
│ │ ├── _helpers.tpl
│ │ ├── ingress.yaml
│ │ └── service.yaml
│ └── values.yaml
└── ui
├── build_info.txt
├── config.ru
├── docker_build.sh
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── helpers.rb
├── middleware.rb
├── ui_app.rb
├── VERSION
└── views
├── create.haml
├── index.haml
├── layout.haml
└── show.haml
- Инициализируем локальный git-репозиторий $ git init
- Добавим удаленный репозиторий $ git remote add origin http://gitlab-gitlab/chromko/ui.git
- Закоммитим и отправим в gitlab $ git add . $ git commit -m “init” $ git push origin master
1) Перенести содержимое директории Charts (папки ui, post, comment, reddit) в Gitlab_ci/reddit-deploy
image: alpine:latest
stages:
- build
- test
- release
- cleanup
build:
stage: build
image: docker:git
services:
- docker:dind
script:
- setup_docker
- build
variables:
DOCKER_DRIVER: overlay2
only:
- branches
test:
stage: test
script:
- exit 0
only:
- branches
release:
stage: release
image: docker
services:
- docker:dind
script:
- setup_docker
- release
only:
- master
.auto_devops: &auto_devops |
[[ "$TRACE" ]] && set -x
export CI_REGISTRY="index.docker.io"
export CI_APPLICATION_REPOSITORY=$CI_REGISTRY/$CI_PROJECT_PATH
export CI_APPLICATION_TAG=$CI_COMMIT_REF_SLUG
export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE="kube-system"
function setup_docker() {
if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
export DOCKER_HOST='tcp://localhost:2375'
fi
fi
}
function release() {
echo "Updating docker images ..."
if [[ -n "$CI_REGISTRY_USER" ]]; then
echo "Logging to GitLab Container Registry with CI credentials..."
docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
echo ""
fi
docker pull "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
docker tag "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" "$CI_APPLICATION_REPOSITORY:$(cat VERSION)"
docker push "$CI_APPLICATION_REPOSITORY:$(cat VERSION)"
echo ""
}
function build() {
echo "Building Dockerfile-based application..."
echo `git show --format="%h" HEAD | head -1` > build_info.txt
echo `git rev-parse --abbrev-ref HEAD` >> build_info.txt
docker build -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
if [[ -n "$CI_REGISTRY_USER" ]]; then
echo "Logging to GitLab Container Registry with CI credentials..."
docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
echo ""
fi
echo "Pushing to GitLab Container Registry..."
docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
echo ""
}
before_script:
- *auto_devops
В текущей конфигурации CI выполняет
1) Build: Сборку докер-образа с тегом master
2) Test: Фиктивное тестирование
3) Release: Смену тега с master на тег из файла VERSION и пуш docker-образа с новым тегом
Job для выполнения каждой задачи запускается в отдельномKubernetes POD-е.
Требуемые операции вызываются в блоках script:
script:
- setup_docker
- build
Описание самих операций производится в виде bash-функций в блоке .auto_devops:
.auto_devops: &auto_devops |
function setup_docker() {
...
}
function release() {
...
}
function build() {
...
}
reddit-deploy/ui/templates/values.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ template "ui.fullname" . }}
annotations:
kubernetes.io/ingress.class: {{ .Values.ingress.class }}
spec:
rules:
- host: {{ .Values.ingress.host | default .Release.Name }}
http:
paths:
- path: / # У nginx свои правила
backend:
serviceName: {{ template "ui.fullname" . }}
servicePort: {{ .Values.service.externalPort }}
reddit-deploy/ui/templates/values.yml
service:
internalPort: 9292
externalPort: 9292
image:
repository: asomir/ui
tag: latest
ingress:
class: nginx # Будем использовать nginx-ingress, который был поставлен вместе с gitlab-ом (так быстрее и правила более гибкие, чем у GCP
postHost:
postPort:
commentHost:
commentPort:
Дадим возможность разработчику запускать отдельное окружение в Kubernetes по коммиту в feature-бранч.
git checkout -b feature/3
image: alpine:latest
stages:
- build
- test
- review
- release
build:
stage: build
image: docker:git
services:
- docker:dind
script:
- setup_docker
- build
variables:
DOCKER_DRIVER: overlay2
only:
- branches
test:
stage: test
script:
- exit 0
only:
- branches
release:
stage: release
image: docker
services:
- docker:dind
script:
- setup_docker
- release
only:
- master
review:
stage: review
script:
- install_dependencies
- ensure_namespace
- install_tiller
- deploy
variables:
KUBE_NAMESPACE: review
host: $CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG
environment:
name: review/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME
url: http://$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG
only:
refs:
- branches
kubernetes: active
except:
- master
.auto_devops: &auto_devops |
[[ "$TRACE" ]] && set -x
export CI_REGISTRY="index.docker.io"
export CI_APPLICATION_REPOSITORY=$CI_REGISTRY/$CI_PROJECT_PATH
export CI_APPLICATION_TAG=$CI_COMMIT_REF_SLUG
export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE="kube-system"
function deploy() {
track="${1-stable}"
name="$CI_ENVIRONMENT_SLUG"
if [[ "$track" != "stable" ]]; then
name="$name-$track"
fi
echo "Clone deploy repository..."
git clone http://gitlab-gitlab/$CI_PROJECT_NAMESPACE/reddit-deploy.git
echo "Download helm dependencies..."
helm dep update reddit-deploy/reddit
echo "Deploy helm release $name to $KUBE_NAMESPACE"
helm upgrade --install \
--wait \
--set ui.ingress.host="$host" \
--set $CI_PROJECT_NAME.image.tag=$CI_APPLICATION_TAG \
--namespace="$KUBE_NAMESPACE" \
--version="$CI_PIPELINE_ID-$CI_JOB_ID" \
"$name" \
reddit-deploy/reddit/
}
function install_dependencies() {
apk add -U openssl curl tar gzip bash ca-certificates git
wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub
wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk
apk add glibc-2.23-r3.apk
rm glibc-2.23-r3.apk
curl https://storage.googleapis.com/pub/gsutil.tar.gz | tar -xz -C $HOME
export PATH=${PATH}:$HOME/gsutil
curl https://kubernetes-helm.storage.googleapis.com/helm-v2.7.2-linux-amd64.tar.gz | tar zx
mv linux-amd64/helm /usr/bin/
helm version --client
curl -o /usr/bin/sync-repo.sh https://raw.githubusercontent.com/kubernetes/helm/master/scripts/sync-repo.sh
chmod a+x /usr/bin/sync-repo.sh
curl -L -o /usr/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x /usr/bin/kubectl
kubectl version --client
}
function setup_docker() {
if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
export DOCKER_HOST='tcp://localhost:2375'
fi
fi
}
function ensure_namespace() {
kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
}
function release() {
echo "Updating docker images ..."
if [[ -n "$CI_REGISTRY_USER" ]]; then
echo "Logging to GitLab Container Registry with CI credentials..."
docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
echo ""
fi
docker pull "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
docker tag "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" "$CI_APPLICATION_REPOSITORY:$(cat VERSION)"
docker push "$CI_APPLICATION_REPOSITORY:$(cat VERSION)"
echo ""
}
function build() {
echo "Building Dockerfile-based application..."
echo `git show --format="%h" HEAD | head -1` > build_info.txt
echo `git rev-parse --abbrev-ref HEAD` >> build_info.txt
docker build -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
if [[ -n "$CI_REGISTRY_USER" ]]; then
echo "Logging to GitLab Container Registry with CI credentials..."
docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
echo ""
fi
echo "Pushing to GitLab Container Registry..."
docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
echo ""
}
function install_tiller() {
echo "Checking Tiller..."
helm init --upgrade
kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy"
if ! helm version --debug; then
echo "Failed to init Tiller."
return 1
fi
echo ""
}
before_script:
- *auto_devops
git commit -am "Add review feature"
git push origin feature/3
Отметим, что мы добавили стадию review, запускающую приложение в k8s по коммиту в feature-бранчи (не master).
review:
stage: review
script:
- install_dependencies
- ensure_namespace
- install_tiller
- deploy
variables:
KUBE_NAMESPACE: review
host: $CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG
environment:
name: review/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME
url: http://$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG
only:
refs:
- branches
kubernetes: active
except:
- master
Мы добавили функцию deploy, которая загружает Chart из репозитория reddit-deploy и делает релиз в неймспейсе review с
образом приложения, собранным на стадии build.
function deploy() {
track="${1-stable}"
name="$CI_ENVIRONMENT_SLUG"
if [[ "$track" != "stable" ]]; then
name="$name-$track"
fi
echo "Clone deploy repository..."
git clone http://gitlab-gitlab/$CI_PROJECT_NAMESPACE/reddit-deploy.git
echo "Download helm dependencies..."
helm dep update reddit-deploy/reddit
echo "Deploy helm release $name to $KUBE_NAMESPACE"
helm upgrade --install \
--wait \
--set ui.ingress.host="$host" \
--set $CI_PROJECT_NAME.image.tag=$CI_APPLICATION_TAG \
--namespace="$KUBE_NAMESPACE" \
--version="$CI_PIPELINE_ID-$CI_JOB_ID" \
"$name" \
reddit-deploy/reddit/
}
helm ls
output
Error: incompatible versions client[v2.9.0-rc3] server[v2.7.2]
helm init --upgrade
$HELM_HOME has been configured at /home/asomir/.helm.
Tiller (the Helm server-side component) has been upgraded to the current version.
Happy Helming!
[asomir@asomir-ubuntu ui]$ helm ls
NAME REVISION UPDATED STATUS CHART NAMESPACE
gitlab 1 Fri Apr 27 15:23:08 2018 DEPLOYED gitlab-omnibus-0.1.37 default
review-asomir-ui-ynrq6y 1 Fri Apr 27 18:30:53 2018 DEPLOYED reddit-0.1.0 review
Созданные для таких целей окружения временны, их требуется “убивать”, когда они больше не нужны.
stop_review:
stage: cleanup
variables:
GIT_STRATEGY: none
script:
- install_dependencies
- delete
environment:
name: review/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME
action: stop
when: manual
allow_failure: true
only:
refs:
- branches
kubernetes: active
except:
- master
stages:
- build
- test
- review
- release
- cleanup
review:
stage: review
environment:
name: review/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME
url: http://$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG
on_stop: stop_review
function delete() {
track="${1-stable}"
name="$CI_ENVIRONMENT_SLUG"
helm delete "$name" --purge || true
}
helm ls
NAME REVISION UPDATED STATUS CHART NAMESPACE
gitlab 1 Fri Apr 27 15:23:08 2018 DEPLOYED gitlab-omnibus-0.1.37 default
Теперь создадим staging и production среды для работы приложения
reddit-deploy/.gitlab-ci.yml
image: alpine:latest
stages:
- test
- staging
- production
test:
stage: test
script:
- exit 0
only:
- triggers
- branches
staging:
stage: staging
script:
- install_dependencies
- ensure_namespace
- install_tiller
- deploy
variables:
KUBE_NAMESPACE: staging
environment:
name: staging
url: http://staging
only:
refs:
- master
kubernetes: active
production:
stage: production
script:
- install_dependencies
- ensure_namespace
- install_tiller
- deploy
variables:
KUBE_NAMESPACE: production
environment:
name: production
url: http://production
when: manual
only:
refs:
- master
kubernetes: active
.auto_devops: &auto_devops |
# Auto DevOps variables and functions
[[ "$TRACE" ]] && set -x
export CI_REGISTRY="index.docker.io"
export CI_APPLICATION_REPOSITORY=$CI_REGISTRY/$CI_PROJECT_PATH
export CI_APPLICATION_TAG=$CI_COMMIT_REF_SLUG
export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE="kube-system"
function deploy() {
echo $KUBE_NAMESPACE
track="${1-stable}"
name="$CI_ENVIRONMENT_SLUG"
helm dep build reddit
# for microservice in $(helm dep ls | grep "file://" | awk '{print $1}') ; do
# SET_VERSION="$SET_VERSION \ --set $microservice.image.tag='$(curl http://gitlab-gitlab/$CI_PROJECT_NAMESPACE/ui/raw/master/VERSION)' "
helm upgrade --install \
--wait \
--set ui.ingress.host="$host" \
--set ui.image.tag="$(curl http://gitlab-gitlab/$CI_PROJECT_NAMESPACE/ui/raw/master/VERSION)" \
--set post.image.tag="$(curl http://gitlab-gitlab/$CI_PROJECT_NAMESPACE/post/raw/master/VERSION)" \
--set comment.image.tag="$(curl http://gitlab-gitlab/$CI_PROJECT_NAMESPACE/comment/raw/master/VERSION)" \
--namespace="$KUBE_NAMESPACE" \
--version="$CI_PIPELINE_ID-$CI_JOB_ID" \
"$name" \
reddit
}
function install_dependencies() {
apk add -U openssl curl tar gzip bash ca-certificates git
wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub
wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk
apk add glibc-2.23-r3.apk
rm glibc-2.23-r3.apk
curl https://kubernetes-helm.storage.googleapis.com/helm-v2.7.2-linux-amd64.tar.gz | tar zx
mv linux-amd64/helm /usr/bin/
helm version --client
curl -L -o /usr/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x /usr/bin/kubectl
kubectl version --client
}
function ensure_namespace() {
kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
}
function install_tiller() {
echo "Checking Tiller..."
helm init --upgrade
kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy"
if ! helm version --debug; then
echo "Failed to init Tiller."
return 1
fi
echo ""
}
function delete() {
track="${1-stable}"
name="$CI_ENVIRONMENT_SLUG"
helm delete "$name" || true
}
before_script:
- *auto_devops
Этот файл отличается от предыдущих тем, что:
- Не собирает docker-образы
- Деплоит на статичные окружения (staging и production)
- Не удаляет окружения
NAME REVISION UPDATED STATUS CHART NAMESPACE
gitlab 1 Fri Apr 27 15:23:08 2018 DEPLOYED gitlab-omnibus-0.1.37 default
production 1 Fri Apr 27 19:55:59 2018 DEPLOYED reddit-0.1.0 production
staging 1 Fri Apr 27 19:52:04 2018 DEPLOYED reddit-0.1.0 staging
Удостоверяемся, что staging успешно завершен. В Environments находим staging. Приложение работает!
Выкатываем на production --> Ручной деплой
И ждем пока пайплайн пройдет
Убеждаемся, что в хелме тоже всё видно.
helm ls
NAME REVISION UPDATED STATUS CHART NAMESPACE
gitlab 1 Fri Apr 27 15:23:08 2018 DEPLOYED gitlab-omnibus-0.1.37 default
production 1 Fri Apr 27 19:55:59 2018 DEPLOYED reddit-0.1.0 production
staging 1 Fri Apr 27 19:52:04 2018 DEPLOYED reddit-0.1.0 staging
Service - определяет конечные узлы доступа (Endpoint’ы):
• селекторные сервисы (k8s сам находит POD-ы по label’ам)
• безселекторные сервисы (мы вручную описываем
конкретные endpoint’ы)
и способ коммуникации с ними (тип (type) сервиса):
• ClusterIP - дойти до сервиса можно только изнутри кластера
• nodePort - клиент снаружи кластера приходит на
опубликованный порт
• LoadBalancer - клиент приходит на облачный (aws elb,
Google gclb) ресурс балансировки
• ExternalName - внешний ресурс по отношению к кластеру
- это виртуальный (в реальности нет интерфейса, pod’а или машины с таким адресом) IP-адрес из диапазона адресов для работы внутри, скрывающий за собой IP-адреса реальных POD-ов. Сервису любого типа (кроме ExternalName) назначается этот IP-адрес.
kubectl get services -n dev
AME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
comment ClusterIP 10.100.228.51 <none> 9292/TCP 2d
comment-db ClusterIP 10.111.159.82 <none> 27017/TCP 2d
post ClusterIP 10.110.188.190 <none> 5000/TCP 2d
post-db ClusterIP 10.110.8.90 <none> 27017/TCP 2d
ui NodePort 10.103.43.159 <none> 9292:31472/TCP 2d
Отметим, что Service - это лишь абстракция и описание того, как
получить доступ к сервису. Но опирается она на реальные
механизмы и объекты: DNS-сервер, балансировщики, iptables.
Для того, чтобы дойти до сервиса, нам нужно узнать его адрес
по имени. Kubernetes не имеет своего собственного DNS-
сервера для разрешения имен. Поэтому используется плагин
kube-dns (это тоже Pod).
Его задачи:
• ходить в API Kubernetes’a и отслеживать Service-объекты
• заносить DNS-записи о Service’ах в собственную базу
• предоставлять DNS-сервис для разрешения имен в IP-адреса
(как внутренних, так и внешних)
Как уже говорилось, ClusterIP - виртуальный и не
принадлежит ни одной реальной физической сущности.
Его чтением и дальнейшими действиями с пакетами,
принадлежащими ему, занимается в нашем случае iptables,
который настраивается утилитой kube-proxy (забирающей
инфу с API-сервера).
Сам kube-proxy, можно настроить на прием трафика, но это
устаревшее поведение и не рекомендуется его применять.
На любой из нод кластера можете посмотреть эти правила
IPTABLES (это не задание).
Kubernetes не имеет в комплекте механизма организации overlay-
сетей (как у Docker Swarm). Он лишь предоставляет интерфейс
для этого. Для создания Overlay-сетей используются отдельные
аддоны: Weave, Calico, Flannel, ... . В Google Kontainer Engine (GKE)
используется собственный плагин kubenet (он - часть kubelet).
Он работает только вместе с платформой GCP и, по-сути
занимается тем, что настраивает google-сети для передачи
трафика Kubernetes. Поэтому в конфигурации Docker сейчас вы
не увидите никаких Overlay-сетей.
Service с типом NodePort - похож на сервис типа
ClusterIP, только к нему прибавляется прослушивание
портов нод (всех нод) для доступа к сервисам снаружи.
При этом ClusterIP также назначается этому сервису для
доступа к нему изнутри кластера.
kube-proxy прослушивается либо заданный порт
(nodePort: 32092), либо порт из диапазона 30000-32670.
Дальше IPTables решает, на какой Pod попадет трафик.
Сервис UI мы уже публиковали наружу с помощью NodePort
apiVersion: v1
kind: Service
metadata:
name: ui
labels:
app: reddit
component: ui
spec:
type: NodePort
ports:
- port: 9292
nodePort: 32092
protocol: TCP
targetPort: 9292
selector:
app: reddit
component: ui
Тип NodePort хоть и предоставляет доступ к сервису снаружи, но открывать все порты наружу или искать IP-
адреса наших нод (которые вообще динамические) не очень удобно.
Тип LoadBalancer позволяет нам использовать внешний облачный балансировщик нагрузки как единую точку
входа в наши сервисы, а не полагаться на IPTables и не открывать наружу весь кластер.
apiVersion: v1
kind: Service
metadata:
name: ui
labels:
app: reddit
component: ui
spec:
type: LoadBalancer
ports:
- port: 80 # Порт, который будет открыт на балансировщике
nodePort: 32092 # Также на ноде будет открыт порт, но нам он не нужен и его можно даже убрать
protocol: TCP
targetPort: 9292 # Порт POD-а
selector:
app: reddit
component: ui
kubectl apply -f ui-service.yml -n dev
kubectl get service -n dev --selector component=ui
output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ui LoadBalancer 10.103.43.159 <pending> 80:32364/TCP 2d
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ui LoadBalancer 10.11.253.252 35.184.38.13 80:32096/TCP 2m
output Microservices Reddit in dev ui-9fbd5c654-67mmx container
Балансировка с помощью Service типа LoadBalancing имеет
ряд недостатков:
• нельзя управлять с помощью http URI (L7-балансировка)
• используются только облачные балансировщики (AWS,
GCP)
• нет гибких правил работы с трафиком
Для более удобного управления входящим снаружи трафиком и решения недостатков
LoadBalancer можно использовать другой объектKubernetes - Ingress.
Ingress – это набор правил внутри кластера Kubernetes, предназначенных для того, чтобы входящие подключения
могли достичь сервисов (Services)
Сами по себе Ingress’ы это просто правила. Для их применения нужен Ingress Controller.
Для работы Ingress-ов необходим Ingress Controller. В отличие остальных контроллеров k8s - он не стартует
вместе с кластером.
Ingress Controller - это скорее плагин (а значит и отдельный POD), который состоит из 2-х функциональных частей:
• Приложение, которое отслеживает через k8s API новые объекты Ingress и обновляет конфигурацию балансировщика
• Балансировщик (Nginx, haproxy, traefik,...), который и занимается управлением сетевым трафиком
Основные задачи, решаемые с помощью Ingress’ов:
• Организация единой точки входа в приложения снаружи
• Обеспечение балансировки трафика
• Терминация SSL
• Виртуальный хостинг на основе имен и т.д
Посколько у нас web-приложение, нам вполне было бы логично использовать L7-балансировщик вместо Service LoadBalancer.
Google в GKE уже предоставляет возможность использовать их собственные решения балансирощик в качестве Ingress controller-ов.
HTTP load balancing Enabled
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ui
spec:
backend:
serviceName: ui
servicePort: 80
Это Singe Service Ingress - значит, что весь ingress контроллер будет просто балансировать нагрузку на Node-ы для одного сервиса
(очень похоже на Service LoadBalancer)
https://console.cloud.google.com/net-services/loadbalancing/loadBalancers/list
Backend
Backend services
1. k8s-be-32096--80cd70df721c608b
Endpoint protocol: HTTP Named port: port32096 Timeout: 30 seconds Health check: k8s-be-32096--80cd70df721c608b Session affinity: None Cloud CDN: disabled Security policy: None
k8s-ig--80cd70df721c608b us-central1-a 2 / 2 Off Max RPS: 1 (per instance) 100%
Выше 32096 NamedPort опубликованного сервиса. Т.е. для работы с Ingress в GCP нам нужен минимум Service с типом NodePort
kubectl get ingress -n dev
NAME HOSTS ADDRESS PORTS AGE
ui * 35.186.253.223 80 13h
http://35.186.253.223/ - адрес сервиса
В текущей схеме есть несколько недостатков:
• у нас 2 балансировщика для 1 сервиса
• Мы не умеем управлять трафиком на уровне HTTP
apiVersion: v1
kind: Service
metadata:
name: ui
labels:
app: reddit
component: ui
spec:
type: NodePort
ports:
- port: 9292
protocol: TCP
targetPort: 9292
selector:
app: reddit
component: ui
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ui
spec:
rules:
- http:
paths:
- path: /*
backend:
serviceName: ui
servicePort: 9292
kubectl get ingress -n dev
NAME HOSTS ADDRESS PORTS AGE
ui * 35.186.253.223 80 9h
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=35.186.253.223"
kubectl create secret tls ui-ingress --key tls.key --cert tls.crt -n dev
kubectl describe secret ui-ingress -n dev
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ui
annotations:
kubernetes.io/ingress.allow-http: "false"
spec:
tls:
- secretName: ui-ingress
backend:
serviceName: ui
servicePort: 9292
kubectl delete -f ui-ingress.yml -n dev
kubectl apply -f ui-ingress.yml -n dev
https://console.cloud.google.com/net-services/loadbalancing/loadBalancers/list
HTTPS 35.186.251.170:443 k8s-ssl-dev-ui--80cd70df721c608b GCP default
Мы будем использовать NetworkPolicy - инструмент для декларативного описания потоков трафика. Отметим,
что не все сетевые плагины поддерживают политики сети.
В частности, у GKE эта функция пока в Beta-тесте и для её работы отдельно будет включен сетевой плагин
Calico (вместо Kubenet).
Давайте ее протеструем. Наша задача - ограничить трафик, поступающий на mongodb отовсюду, кроме сервисов post и comment.
gcloud beta container clusters list
output
WARNING: You invoked `gcloud beta`, but with current configuration Kubernetes Engine v1 API will be used instead of v1beta1 API.
`gcloud beta` will switch to use Kubernetes Engine v1beta1 API by default by the end of March 2018.
If you want to keep using `gcloud beta` to talk to v1 API temporarily, please set `container/use_v1_api` property to true.
But we will drop the support for this property at the beginning of May 2018, please migrate if necessary.
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
cluster-1 us-central1-a 1.8.8-gke.0 35.188.139.129 g1-small 1.8.8-gke.0 2 RUNNING
gcloud beta container clusters update cluster-1 --zone=us-central1-a --update-addons=NetworkPolicy=ENABLED
output
WARNING: You invoked `gcloud beta`, but with current configuration Kubernetes Engine v1 API will be used instead of v1beta1 API.
`gcloud beta` will switch to use Kubernetes Engine v1beta1 API by default by the end of March 2018.
If you want to keep using `gcloud beta` to talk to v1 API temporarily, please set `container/use_v1_api` property to true.
But we will drop the support for this property at the beginning of May 2018, please migrate if necessary.
Updating cluster-1...done.
Updated [https://container.googleapis.com/v1/projects/docker-194414/zones/us-central1-a/clusters/cluster-1].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-a/cluster-1?project=docker-194414
gcloud beta container clusters update cluster-1 --zone=us-central1-a --enable-network-policy
output
WARNING: You invoked `gcloud beta`, but with current configuration Kubernetes Engine v1 API will be used instead of v1beta1 API.
`gcloud beta` will switch to use Kubernetes Engine v1beta1 API by default by the end of March 2018.
If you want to keep using `gcloud beta` to talk to v1 API temporarily, please set `container/use_v1_api` property to true.
But we will drop the support for this property at the beginning of May 2018, please migrate if necessary.
Updating cluster-1...done.
Updated [https://container.googleapis.com/v1/projects/docker-194414/zones/us-central1-a/clusters/cluster-1].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-a/cluster-1?project=docker-194414
[asomir@asomir-ubuntu kubernetes]$ gcloud beta container clusters update cluster-1 --zone=us-central1-a --enable-network-policy
WARNING: You invoked `gcloud beta`, but with current configuration Kubernetes Engine v1 API will be used instead of v1beta1 API.
`gcloud beta` will switch to use Kubernetes Engine v1beta1 API by default by the end of March 2018.
If you want to keep using `gcloud beta` to talk to v1 API temporarily, please set `container/use_v1_api` property to true.
But we will drop the support for this property at the beginning of May 2018, please migrate if necessary.
Enabling/Disabling Network Policy causes a rolling update of all
cluster nodes, similar to performing a cluster upgrade. This
operation is long-running and will block other operations on the
cluster (including delete) until it has run to completion.
Do you want to continue (Y/n)? y
Updating cluster-1...done.
Updated [https://container.googleapis.com/v1/projects/docker-194414/zones/us-central1-a/clusters/cluster-1].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-a/cluster-1?project=docker-194414
mongo-network-policy.yml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-db-traffic
labels:
app: reddit
spec:
podSelector: # Выбираем объекты политики (pod’ы с mongodb)
matchLabels:
app: reddit
component: mongo
policyTypes: # Блок запрещающих направлений Запрещаем все входящие подключения Исходящие разрешены
- Ingress
ingress: # Блок разрешающих правил (whitelist)
- from:
- podSelector:
matchLabels:
app: reddit # Разрешаем все входящие подключения от
component: comment # POD-ов с label-ами comment.
kubectl apply -f mongo-network-policy.yml -n dev
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-db-traffic
labels:
app: reddit
spec:
podSelector:
matchLabels:
app: reddit
component: mongo
policyTypes:
- Ingress
ingress:
- from:
- podSelector: # Разрешаем камменту
matchLabels:
app: reddit
component: comment
- podSelector: # Разрешаем посту
matchLabels:
app: reddit
component: post
Проверяем https://35.186.251.170/ - можно постить и камментить!
Рассмотрим вопросы хранения данных. Основной Stateful сервис в нашем приложении - это база данных MongoDB.
В текущий момент она запускается в виде Deployment и хранит данные в стаднартный Docker Volume-ах. Это имеет несколько проблем:
- при удалении POD-а удаляется и Volume
- потеря Nod’ы с mongo грозит потерей данных
- запуск базы на другой ноде запускает новый экземпляр данных
mongo-deployment.yml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: mongo
labels:
app: reddit
component: mongo
post-db: "true"
comment-db: "true"
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: mongo
template:
metadata:
name: mongo
labels:
app: reddit
component: mongo
post-db: "true"
comment-db: "true"
spec:
containers:
- image: mongo:3.2
name: mongo
volumeMounts: # В этом месте подключаем Волюм
- name: mongo-persistent-storage
mountPath: /data/db
volumes:
- name: mongo-persistent-storage # В этом месте объявляем Волюм
emptyDir: {}
Сейчас используется тип Volume emptyDir. При создании пода с таким типом просто создается пустой docker volume.
При остановке POD’a содержимое emtpyDir удалится навсегда. Хотя в общем случае падение POD’a не вызывает удаления Volume’a.
- создайте пост в приложении
- удалите deployment для mongo
kubectl delete -n dev -f mongo-deployment.yml
- Создайте его заново
kubectl apply -n dev -f mongo-deployment.yml
Видим, что пропали все сообщения :(
Вместо того, чтобы хранить данные локально на ноде, имеет смысл подключить удаленное хранилище. В нашем случае можем
использовать Volume gcePersistentDisk, который будет складывать данные в хранилище GCE.
gcloud compute disks create --size=25GB --zone=us-central1-a reddit-mongo-disk
output
WARNING: You have selected a disk size of under [200GB]. This may result in poor I/O performance. For more information, see: https://developers.google.com/compute/docs/disks#performance.
Created [https://www.googleapis.com/compute/v1/projects/docker-194414/zones/us-central1-a/disks/reddit-mongo-disk].
NAME ZONE SIZE_GB TYPE STATUS
reddit-mongo-disk us-central1-a 25 pd-standard READY
New disks are unformatted. You must format and mount a disk before it
can be used. You can find instructions on how to do this at:
https://cloud.google.com/compute/docs/disks/add-persistent-disk#formatting
mongo-deployment.yml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: mongo
labels:
app: reddit
component: mongo
post-db: "true"
comment-db: "true"
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: mongo
template:
metadata:
name: mongo
labels:
app: reddit
component: mongo
post-db: "true"
comment-db: "true"
spec:
containers:
- image: mongo:3.2
name: mongo
volumeMounts:
- name: mongo-gce-pd-storage
mountPath: /data/db
volumes:
- name: mongo-persistent-storage
emptyDir: {}
volumes:
- name: mongo-gce-pd-storage # Меняем Volume на другой тип
gcePersistentDisk:
pdName: reddit-mongo-disk
fsType: ext4
kubectl apply -n dev -f mongo-deployment.yml
Дожидаемся пересоздания Pod'а (занимает до 10 минут). Зайдем в приложение и добавим пост. Удаляем пост
kubectl delete deploy mongo -n dev
kubectl apply -n dev -f mongo-deployment.yml
Заходим на наш уютненький форум, - наш пост все еще на месте
https://console.cloud.google.com/compute/disks
Используемый механизм Volume-ов можно сделать удобнее. Мы можем использовать не целый выделенный диск для
каждого пода, а целый ресурс хранилища, общий для всего кластера.
Тогда при запуске Stateful-задач в кластере, мы сможем запросить хранилище в виде такого же ресурса, как CPU или
оперативная память.
Для этого будем использовать механизм PersistentVolume.
mongo-volume.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: reddit-mongo-disk # Имя PersistentVolume'а
spec:
capacity:
storage: 25Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
gcePersistentDisk:
fsType: "ext4"
pdName: "reddit-mongo-disk" # Имя диска в GCE
kubectl apply -f mongo-volume.yml -n dev
Мы создали PersistentVolume в виде диска в GCP.
Мы создали ресурс дискового хранилища, распространенный на весь кластер, в виде PersistentVolume.
Чтобы выделить приложению часть такого ресурса - нужно создать запрос на выдачу - PersistentVolumeClaim.
Claim - это именно запрос, а не само хранилище. С помощью запроса можно выделить место как из
конкретного PersistentVolume (тогда параметры accessModes и StorageClass должны соответствовать, а места должно
хватать), так и просто создать отдельный PersistentVolume под конкретный запрос
mongo-claim.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: mongo-pvc # Имя PersistentVolumeClame'а
spec:
accessModes:
- ReadWriteOnce # accessMode у PVC и у PV должен совпадать
resources:
requests:
storage: 25Gi
kubectl apply -f mongo-claim.yml -n dev
Мы выделили место в PV по запросу для нашей базы. Одновременно использовать один PV можно только по одному Claim’у
Если Claim не найдет по заданным параметрам PV внутри кластера, либо тот будет занят другим Claim’ом
то он сам создаст нужный ему PV воспользовавшись стандартным StorageClass.
kubectl describe storageclass standard -n dev
Name: standard
IsDefaultClass: Yes
Annotations: storageclass.beta.kubernetes.io/is-default-class=true
Provisioner: kubernetes.io/gce-pd
Parameters: type=pd-standard
AllowVolumeExpansion: <unset>
MountOptions: <none>
ReclaimPolicy: Delete
VolumeBindingMode: Immediate
Events: <none>
В нашем случае это обычный медленный Google Cloud Persistent Drive
mongo-deployment.yml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: mongo
labels:
app: reddit
component: mongo
post-db: "true"
comment-db: "true"
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: mongo
template:
metadata:
name: mongo
labels:
app: reddit
component: mongo
post-db: "true"
comment-db: "true"
spec:
containers:
- image: mongo:3.2
name: mongo
volumeMounts:
- name: mongo-gce-pd-storage
mountPath: /data/db
volumes: # Имя PersistentVolumeClame'а
- name: mongo-gce-pd-storage
persistentVolumeClaim:
claimName: mongo-pvc
kubectl apply -f mongo-deployment.yml -n dev
KUBERNETES CLUSTER GCP
Mongo Reddit-mongo-disk
| | }
PVC | } 15Gb } 25Gb
| | } }
PV---------------------------
Создав PersistentVolume мы отделили объект "хранилища" от наших Service'ов и Pod'ов. Теперь мы можем его при
необходимости переиспользовать.
Но нам гораздо интереснее создавать хранилища при необходимости и в автоматическом режиме. В этом нам
помогут StorageClass’ы. Они описывают где (какой провайдер) и какие хранилища создаются.
В нашем случае создадим StorageClass Fast так, чтобы монтировались SSD-диски для работы нашего хранилища.
storage-fast.yml
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
name: fast # Имя StorageClass'а
provisioner: kubernetes.io/gce-pd # Провайдер хранилища
parameters:
type: pd-ssd # Тип предоставляемого хранилища
kubectl apply -f storage-fast.yml -n dev
mongo-claim-dynamic.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: mongo-pvc-dynamic
spec:
accessModes:
- ReadWriteOnce
storageClassName: slow # Вместо ссылки на созданный диск, теперь мы ссылаемся на StorageClass
resources:
requests:
storage: 10Gi
kubectl apply -f mongo-claim-dynamic.yml -n dev
mongo-deployment.yml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: mongo
labels:
app: reddit
component: mongo
post-db: "true"
comment-db: "true"
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: mongo
template:
metadata:
name: mongo
labels:
app: reddit
component: mongo
post-db: "true"
comment-db: "true"
spec:
containers:
- image: mongo:3.2
name: mongo
volumeMounts:
- name: mongo-gce-pd-storage
mountPath: /data/db
volumes:
- name: mongo-gce-pd-storage
persistentVolumeClaim:
claimName: mongo-pvc-dynamic # Обновим PersistentVolumeClaim
kubectl apply -f mongo-deployment.yml -n dev
kubectl get persistentvolume -n dev
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-d594c449-3ff1-11e8-a12e-42010a8000b4 25Gi RWO Delete Bound dev/mongo-pvc standard 28m
reddit-mongo-disk 25Gi RWO Retain Available 46m
KUBERNETES CLUSTER GCP
Mongo Reddit-mongo-disk
| | }
PVC mongo-pvc-dynamic | } 15Gb } 25Gb
| | } 10Gb } }
PV pvc-d594c449 SSD
На созданные Kubernetes'ом диски можно посмотреть в web console
https://console.cloud.google.com/compute/disks
Для дальнейшей работы нам нужно подготовить локальное окружение, которое будет состоять из:
- kubectl - фактически, главной утилиты для работы c Kubernetes API (все, что делает kubectl, можно сделать с помощью HTTP-запросов к API k8s)
- Директории ~/.kube - содержит служебную инфу для kubectl (конфиги, кеши, схемы API)
- minikube - утилиты для разворачивания локальной инсталляции Kubernetes.
Необходимо установить Kubectl: https://kubernetes.io/docs/tasks/tools/install-kubectl/
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.24.1/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
minikube start
output
Starting VM...
Getting VM IP address...
Moving files into cluster...
Downloading localkube binary
148.25 MB / 148.25 MB [============================================] 100.00% 0s
0 B / 65 B [----------------------------------------------------------] 0.00%
65 B / 65 B [======================================================] 100.00% 0sSetting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.
Наш Minikube-кластер развернут. При этом автоматически был настроен конфиг kubectl. Проверим, что это так:
kubectl get nodes
output
NAME STATUS ROLES AGE VERSION
minikube Ready <none> 51s v1.8.0
####### Конфигурация kubectl - это контекст. Контекст - это комбинация:
- cluster - API-сервер
- user - пользователь для подключения к кластеру
- namespace - область видимости (не обязательно, по умолчанию default)
Информацию о контекстах kubectl сохраняет в файле ~/.kube/config - это такой же манифест kubernetes в YAML-формате (есть и Kind, и ApiVersion).
- server - адрес kubernetes API-сервера
- certificate-authority - корневой сертификат (которым подписан SSL-сертификат самого сервера), чтобы убедиться, что нас не обманывают и перед нами тот самый сервер
- name (Имя) для идентификации в конфиге
apiVersion: v1
clusters:
- cluster:
certificate-authority: /home/asomir/.minikube/ca.crt
server: https://192.168.99.100:8443
name: minikube
- Данные для аутентификации (зависит от того, как настроен сервер). Это могут быть: • username + password (Basic Auth • client key + client certificate • token • auth-provider config (например GCP)
- name (Имя) для идентификации в конфиге
users:
- name: minikube
user:
as-user-extra: {}
client-certificate: /home/asomir/.minikube/client.crt
client-key: /home/asomir/.minikube/client.key
- cluster - имя кластера из списка clusters
- user - имя пользователя из списка users
- namespace - область видимости по-умолчанию (не обязательно)
- name (Имя) для идентификации в конфиге
contexts:
- context:
cluster: minikube
user: minikube
name: minikube
- Создать cluster :
kubectl config set-cluster ... cluster_name
- Создать данные пользователя (credentials)
kubectl config set-credentials ... user_name
- Создать контекст
kubectl config set-context context_name
--cluster=cluster_name
--user=user_name
- Использовать контекст
kubectl config use-context context_name
Таким образом kubectl конфигурируется для подключения к разным кластерам, под разными пользователями.
kubectl config current-context
output
minikube
Список всех контекстов можно увидеть так:
kubectl config get-contexts
output
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* minikube minikube minikube
Для работы в приложения kubernetes, нам необходимо описать их желаемое состояние либо в YAML-манифестах, либо с помощью командной строки. Основные объекты - это ресурсы Deployment.
Как помним из предыдущего занятия, его основные задачи:
• Создание ReplicationSet (следит, чтобы число запущенных Pod-ов соответствовало описанному)
• Ведение истории версий запущенных Pod-ов (для различных стратегий деплоя, для возможностей отката)
• Описание процесса деплоя (стратегия, параметры стратегий)
apiVersion: apps/v1beta1
kind: Deployment
metadata: # блок метаданных деплоя
name: ui
labels:
app: reddit
component: ui
spec: # блок спецификации деплоя
replicas: 3
selector: # описывает, как отслеживать поды, - контроллер будет считать поды с метками app=reddit И component=ui
matchLabels:
app: reddit
component: ui
template: # блок описания подов
metadata:
name: ui-pod
labels: # поэтому важно в описаниия пода задать нужные лабельки
app: reddit # Для более гибкой выборки вводим 2
component: ui # метки (app и component).
spec:
containers:
- image: asomir/ui # какой берём образ для деплоя
name: ui
kubectl apply -f ui-deployment.yml
output
deployment.apps "ui" created
kubectl get deployment
output вначале показал
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
ui 3 3 3 0 1m
затем через несколько секунд исправился
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
ui 3 3 3 0 1m
P.S. kubectl apply -f может принимать не только отдельный файл, но и папку с ними. Например: kubectl apply -f ./kube
Пока что мы не можем использовать наше приложение полностью,потому что никак не настроена сеть для общения с ним. Но kubectl умеет пробрасывать сетевые порты POD-ов на локальную машину
kubectl get pods --selector component=ui
output
NAME READY STATUS RESTARTS AGE
ui-bf7c99cb8-2pl8d 1/1 Running 0 5m
ui-bf7c99cb8-kzn6l 1/1 Running 0 5m
ui-bf7c99cb8-tb2z7 1/1 Running 0 5m
kubectl port-forward ui-bf7c99cb8-2pl8d 8080:9292
Зайдем в браузере на http://localhost:8080
Microservices Reddit in ui-bf7c99cb8-2pl8d container
Can't show blog posts, some problems with the post service. Refresh?
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: post
labels:
app: reddit
component: post
spec:
replicas: 3
selector:
matchLabels:
app: reddit
component: post
template:
metadata:
name: post
labels:
app: reddit
component: post
spec:
containers:
- image: asomir/post
name: post
kubectl apply -f post-deployment.yml
output
deployment.apps "post" created
kubectl get deployment
output
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
post 3 3 3 3 42s
ui 3 3 3 3 13m
kubectl get pods --selector component=post
output
NAME READY STATUS RESTARTS AGE
post-59f6d967f8-77clg 1/1 Running 0 3m
post-59f6d967f8-g8dxq 1/1 Running 0 3m
post-59f6d967f8-rljsd 1/1 Running 0 3m
kubectl port-forward post-59f6d967f8-77clg 8080:5000
Пойдём по ссылке, чтобы проверить хелсчек http://localhost:8080/healthcheck
{"status": 0, "dependent_services": {"postdb": 0}, "version": "0.0.2"}
Deployment компоненты Comment сконфигурируйте подобным же образом и проверьте. Не забудьте, что comment слушает по-умолчанию на порту 9292
kubectl apply -f comment-deployment.yml
output
deployment.apps "comment" created
kubectl get deployment
output
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
comment 3 3 3 3 2m
post 3 3 3 3 25m
ui 3 3 3 3 37m
kubectl get pods --selector component=comment
output
NAME READY STATUS RESTARTS AGE
comment-56845dfcd6-qdp9d 1/1 Running 0 2m
comment-56845dfcd6-w4vv5 1/1 Running 0 2m
comment-56845dfcd6-zkrl4 1/1 Running 0 2m
kubectl port-forward comment-56845dfcd6-qdp9d 8080:9292
Пойдём по ссылке, чтобы проверить хелсчек http://localhost:8080/healthcheck
{"status":0,"dependent_services":{"commentdb":0},"version":"0.0.3"}
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: mongo
labels:
app: reddit
component: mongo
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: mongo
template:
metadata:
name: mongo
labels:
app: reddit
component: mongo
spec:
containers:
- image: mongo:3.2
name: mongo
volumeMounts: # Точка монтирования в контейнере
- name: mongo-persistent-storage
mountPath: /data/db
volumes: # Ассоциированные с подом волюмы
- name: mongo-persistent-storage
emptyDir: {}
В текущем состоянии приложение не будет работать, так его компоненты ещё не знают как найти друг друга Для связи компонент между собой и с внешним миром используется объект Service - абстракция, которая определяет набор POD-ов (Endpoints) и способ доступа к ним
post-service.yml
apiVersion: v1
kind: Service
metadata:
# Когда объект service будет создан:
name: post # В DNS появится запись для post
labels:
app: reddit
component: post
spec:
ports:
- port: 5000 # 2) При обращении на адрес post:5000 изнутри любого из POD-ов текущего namespace
protocol: TCP
targetPort: 5000 # нас переправит на 5000-ный порт одного из POD-ов приложения post,
selector: # выбранных по label-ам selector-ом
app: reddit
component: post
kubectl apply -f post-service.yml
kubectl describe service post | grep Endpoints
output
Endpoints: 172.17.0.7:5000,172.17.0.8:5000,172.17.0.9:5000
kubectl get pods
output
NAME READY STATUS RESTARTS AGE
comment-56845dfcd6-qdp9d 1/1 Running 0 1h
comment-56845dfcd6-w4vv5 1/1 Running 0 1h
comment-56845dfcd6-zkrl4 1/1 Running 0 1h
post-59f6d967f8-77clg 1/1 Running 0 1h
post-59f6d967f8-g8dxq 1/1 Running 0 1h
post-59f6d967f8-rljsd 1/1 Running 0 1h
ui-bf7c99cb8-2pl8d 1/1 Running 0 1h
ui-bf7c99cb8-kzn6l 1/1 Running 0 1h
ui-bf7c99cb8-tb2z7 1/1 Running 0 1h
kubectl exec -ti comment-56845dfcd6-qdp9d nslookup post
rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:262: starting container process caused "exec: \"nslookup\": executable file not found in $PATH"
command terminated with exit code 126
По аналогии создайте объект Service в файле comment-service.yml для Comment (не забудьте про label-ы и правильные tcp-порты).
apiVersion: v1
kind: Service
metadata:
name: comment
labels:
app: reddit
component: comment
spec:
ports:
- port: 9292
protocol: TCP
targetPort: 9292
selector:
app: reddit
component: comment
kubectl apply -f comment-service.yml
output
service "comment" created
mongodb-service.yml
apiVersion: v1
kind: Service
metadata:
name: mongodb
labels:
app: reddit
component: mongo
spec:
ports:
- port: 27017
protocol: TCP
targetPort: 27017
selector:
app: reddit
component: mongo
kubectl apply -f mongodb-service.yml
output
service "mongodb" created
kubectl get services
output
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
comment ClusterIP 10.101.71.166 <none> 9292/TCP 7m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4h
mongodb ClusterIP 10.100.225.155 <none> 27017/TCP 44s
post ClusterIP 10.109.136.84 <none> 5000/TCP 19m
kubectl port-forward ui-bf7c99cb8-2pl8d 9292:9292
output
Forwarding from 127.0.0.1:9292 -> 9292
Forwarding from [::1]:9292 -> 9292
Заходим на http://localhost:9292
kubectl logs comment-56845dfcd6-w4vv5
Приложение ищет совсем другой адрес: comment_db, а не mongodb Аналогично и сервис post ищет post_db. Эти адреса заданы в их Dockerfile-ах в виде переменных окружения:
post/Dockerfile
...
ENV POST_DATABASE_HOST=post_db
comment/Dockerfile
...
ENV COMMENT_DATABASE_HOST=comment_db
В Docker Swarm проблема доступа к одному ресурсу под разными именами решалась с помощью сетевых алиасов. В Kubernetes такого функционала нет. Мы эту проблему можем решить с помощью тех же Service-ов.
comment-mongodb-service.yml
apiVersion: v1
kind: Service
metadata:
name: comment-db # В имени нельзя использовать “_”
labels:
app: reddit
component: mongo
comment-db: "true" # добавим метку, чтобы различать сервисы
spec:
ports:
- port: 27017
protocol: TCP
targetPort: 27017
selector:
app: reddit
component: mongo
comment-db: "true" # Отдельный лейбл для comment-db
P.S. булевые значения обязательно указывать в кавычках
kubectl apply -f comment-mongodb-service.yml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: comment
labels:
app: reddit
component: comment
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: comment
template:
metadata:
name: comment
labels:
app: reddit
component: comment
spec:
containers:
- image: asomir/comment
name: comment
env: # переменная окружения для обращения к базе
- name: COMMENT_DATABASE_HOST
value: comment-db
kubectl delete -f comment-deployment.yml
kubectl apply -f comment-deployment.yml
kubectl delete -f mongodb-service.yml
post-mongodb-service.yml
gcloud beta container clusters update cluster-1 --zone=us-central1-a --enable-network-policy
apiVersion: v1
kind: Service
metadata:
name: post-db
labels:
app: reddit
component: mongo
post-db: "true"
spec:
ports:
- port: 27017
protocol: TCP
targetPort: 27017
selector:
app: reddit
component: mongo
post-db: "true"
mongo-deployment.yml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: mongo
labels:
app: reddit
component: mongo
comment-db: "true"
post-db: "true" # вот здесь для поста
spec:
replicas: 1
selector:
matchLabels:
app: reddit
component: mongo
template:
metadata:
name: mongo
labels:
app: reddit
component: mongo
comment-db: "true"
post-db: "true" # и здесь для поста
spec:
containers:
- image: mongo:3.2
name: mongo
post-deployment.yml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: post
labels:
app: reddit
component: post
spec:
replicas: 3
selector:
matchLabels:
app: reddit
component: post
template:
metadata:
name: post
labels:
app: reddit
component: post
spec:
containers:
- image: asomir/post
name: post
env: # собсна, переменная окружения
- name: COMMENT_DATABASE_HOST
value: post-db
kubectl delete -f post-deployment.yml
kubectl apply -f post-deployment.yml
kubectl apply -f post-mongodb-service.yml
kubectl delete -f mongo-deployment.yml
kubectl apply -f mongo-deployment.yml
Нам нужно как-то обеспечить доступ к ui-сервису снаружи. Для этого нам понадобится Service для UI-компоненты
ui-service.yml
apiVersion: v1
kind: Service
metadata:
name: ui
labels:
app: reddit
component: ui
spec:
type: NodePort # Главное отличие - тип сервиса NodePort.
ports:
- port: 9292
protocol: TCP
targetPort: 9292
selector:
app: reddit
component: ui
По-умолчанию все сервисы имеют тип ClusterIP - это значит, что сервис распологается на внутреннем диапазоне IP-адресов кластера. Снаружи до него нет доступа. Тип NodePort - на каждой ноде кластера открывает порт из диапазона 30000-32767 и переправляет трафик с этого порта на тот, который указан в targetPort Pod (похоже на стандартный expose в docker) Теперь до сервиса можно дойти по :
apiVersion: v1
kind: Service
metadata:
name: ui
labels:
app: reddit
component: ui
spec:
type: NodePort
ports:
- nodePort: 32092 # вот так вот
port: 9292
protocol: TCP
targetPort: 9292
selector:
app: reddit
component: ui
Т.е. в описании service NodePort - для доступа снаружи кластера port - для доступа к сервису изнутри кластера
minikube service ui
Minikube может перенаправлять на web-странцы с сервисами которые были помечены типом NodePort. Список сервисов:
minikube service list
|-------------|----------------------|-----------------------------|
| NAMESPACE | NAME | URL |
|-------------|----------------------|-----------------------------|
| default | comment | No node port |
| default | comment-db | No node port |
| default | kubernetes | No node port |
| default | post | No node port |
| default | post-db | No node port |
| default | ui | http://192.168.99.100:32092 |
| kube-system | kube-dns | No node port |
| kube-system | kubernetes-dashboard | http://192.168.99.100:30000 |
|-------------|----------------------|-----------------------------|
Minikube также имеет в комплекте несколько стандартных аддонов (расширений) для Kubernetes (kube-dns, dashboard, monitoring,...). Каждое расширение - это такие же PODы и сервисы, какие создавались нами, только они еще общаются с API самого Kubernetes
minikube addons list
- addon-manager: enabled
- coredns: disabled
- dashboard: enabled
- default-storageclass: enabled
- efk: disabled
- freshpod: disabled
- heapster: disabled
- ingress: disabled
- kube-dns: enabled
- registry: disabled
- registry-creds: disabled
- storage-provisioner: enabled
minikube addons enable dashboard
kubectl get pods
NAME READY STATUS RESTARTS AGE
comment-5d5fb96865-g8dmx 1/1 Running 1 15h
comment-5d5fb96865-mqxgf 1/1 Running 2 15h
comment-5d5fb96865-zsjgp 1/1 Running 2 15h
mongo-7756fd9fbb-k28hj 1/1 Running 1 15h
post-7f48cf85d9-bv2p9 1/1 Running 1 15h
post-7f48cf85d9-q48fj 1/1 Running 1 15h
post-7f48cf85d9-xth9c 1/1 Running 1 15h
ui-5b9965d589-8lqbz 1/1 Running 1 15h
ui-5b9965d589-whdvh 1/1 Running 1 15h
ui-5b9965d589-zwspp 1/1 Running 1 15h
То есть вообще ничего нового. Поды и сервисы дашборда были запущены в неймспейсах kube-system. Когда мы просто жамкаем, запрашивается пространство имён default
Namespace - это, по сути, виртуальный кластер Kubernetes внутри самого Kubernetes. Внутри каждого такого кластера находятся свои объекты (POD-ы, Service-ы, Deployment-ы и т.д.), кроме объектов, общих на все namespace-ы (nodes, ClusterRoles, PersistentVolumes)
В разных namespace-ах могут находится объекты с одинаковым именем, но в рамках одного namespace имена объектов должны быть уникальны.
При старте Kubernetes кластер уже имеет 3 namespace:
• default - для объектов для которых не определен другой
Namespace (в нем мы работали все это время)
• kube-system - для объектов созданных Kubernetes’ом и
для управления им
• kube-public - для объектов к которым нужен доступ из
любой точки кластера
Для того, чтобы выбрать конкретное пространство имен, нужно указать
флаг -n <namespace> или --namespace <namespace> при запуске kubect
kubectl get all -n kube-system --selector app=kubernetes-dashboard
output
NAME READY STATUS RESTARTS AGE
kubernetes-dashboard-77d8b98585-pb8lb 1/1 Running 7 3d
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-dashboard NodePort 10.101.197.233 <none> 80:30000/TCP 5d
NAME DESIRED CURRENT READY AGE
kubernetes-dashboard-77d8b98585 1 1 1 3d
kubernetes-dashboard-77d8b98585 1 1 1 3d
Мы вывели все объекты из неймспейса kube-system, имеющие label app=kubernetes-dashboard
minikube service kubernetes-dashboard -n kube-system
В самом Dashboard можно:
• отслеживать состояние кластера и рабочих нагрузок в нем
• создавать новые объекты (загружать YAML-файлы)
• Удалять и изменять объекты (кол-во реплик, yaml-файлы)
• отслеживать логи в Pod-ах
• при включении Heapster-аддона смотреть нагрузку на Pod-
ах
• и т.д.
Используем же namespace в наших целях. Отделим среду для разработки приложения от всего остального кластера.
dev-namespace.yml
apiVersion: v1
kind: Namespace
metadata:
name: dev
kubectl apply -f dev-namespace.yml
kubectl apply -n dev -f comment-deployment.yml
kubectl apply -n dev -f mongo-deployment.yml
kubectl apply -n dev -f post-deployment.yml
kubectl apply -n dev -f ui-deployment.yml
kubectl apply -n dev -f comment-mongodb-service.yml
kubectl apply -n dev -f post-mongodb-service.yml
kubectl apply -n dev -f post-service.yml
kubectl apply -n dev -f comment-service.yml
kubectl apply -n dev -f ui-service.yml
output The Service "ui" is invalid: spec.ports[0].nodePort: Invalid value: 32092: provided port is already allocated
Опачки! Убираем NodePort из ui-service
minikube service ui -n dev
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: ui
labels:
app: reddit
component: ui
spec:
replicas: 3
selector:
matchLabels:
app: reddit
component: ui
template:
metadata:
name: ui-pod
labels:
app: reddit
component: ui
spec:
containers:
- image: asomir/ui:latest
name: ui
env:
- name: ENV # Извлекаем значения из контекста запуска
valueFrom:
fieldRef:
fieldPath: metadata.namespace
kubectl apply -f ui-deployment.yml -n dev
В браузере видим Microservices Reddit in dev ui-7c865f78d5-j2fjf container
Мы подготовили наше приложение в локальном окружении. Теперь самое время запустить его на реальном кластере Kubernetes. В качестве основной платформы будем использовать Google Kubernetes Engine.
gcloud container clusters get-credentials cluster-1 --zone us-central1-a --project docker-194414
output Fetching cluster endpoint and auth data. kubeconfig entry generated for cluster-1.
В результате в файл ~/.kube/config будут добавлены user, cluster и context для подключения к кластеру в GKE. Также текущий контекст будет выставлен для подключения к этому кластеру. Убедиться можно, введя
kubectl config current-context
output gke_docker-194414_us-central1-a_cluster-1
kubectl apply -f dev-namespace.yml
output namespace "dev" created
kubectl apply -f . -n dev
output deployment.apps "comment" created service "comment-db" created service "comment" created namespace "dev" configured deployment.apps "mongo" created service "mongodb" created deployment.apps "post" created service "post-db" created service "post" created deployment.apps "ui" created service "ui" created
Откроем диапазон портов kubernetes для публикации
сервисов
Настройте:
• Название - произвольно, но понятно
• Целевые экземпляры - все экземпляры в сети
• Диапазоны IP-адресов источников - 0.0.0.0/0
Протоколы и порты - Указанные протоколы и порты
tcp:30000-32767
kubectl get nodes -o wide
output NAME STATUS ROLES AGE VERSION EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME gke-cluster-1-default-pool-bb385f06-8c63 Ready 13m v1.8.8-gke.0 35.188.17.21 Container-Optimized OS from Google 4.4.111+ docker://17.3.2 gke-cluster-1-default-pool-bb385f06-qk9b Ready 13m v1.8.8-gke.0 35.184.151.174 Container-Optimized OS from Google 4.4.111+ docker://17.3.2
kubectl describe service ui -n dev | grep NodePort
Type: NodePort
NodePort: <unset> 31041/TCP
Идём по адресу http://35.188.17.21:31041/
output Microservices Reddit in dev ui-9fbd5c654-wdbf9 container
В GKE также можно запустить Dashboard для кластера. Жмём на кнопку edit. В появившемся меню кликаем Add-ons - Kubernetes dashboard - Enabled Ждем пока кластер загрузится
kubectl proxy
output Starting to serve on 127.0.0.1:8001
Заходим по адресу http://localhost:8001/ui
Не пущает
Так произошло, потому что dashboard - это аддон и он подключается к API kubernetes. API его не пустило по причине того, что оно ничего не знает о пользователе (service account-е) system:serviceaccount:kube-system:default
kubectl create sa kubernetes-dashboard -n kube-system
Нужно нашему Service Account назначить роль с достаточными правами на просмотр информации о кластере В кластере уже есть объект ClusterRole с названием cluster-admin. Тот, кому назначена эта роль имеет полный доступ ко всем объектам кластера. Давайте назначим эту роль service account-у dashboard-а с помощью clusterrolebinding (привязки)
kubectl create clusterrolebinding kubernetes-dashboard --clusterrole=cluster-admin --serviceaccount=kube-system:kubernetes-dashboard
output clusterrolebinding.rbac.authorization.k8s.io "kubernetes-dashboard" created
465e067982737ac4cef4342160ffe51a15ee3262
Опишем приложение в контексте Kubernetes с помощью manifest-ов в YAML-формате. Основным примитивом будет Deployment.
Основные задачи сущности Deployment:
• Создание Replication Controller-а (следит, чтобы число запущенных Pod-ов соответствовало описанному)
• Ведение истории версий запущенных Pod-ов (для различных стратегий деплоя, для возможностей отката)
• Описание процесса деплоя (стратегия, параметры стратегий) По ходу курса эти манифесты будут обновляться, а также появляться новые. Текущие файлы нужны для создания структуры и проверки работоспособности kubernetes-кластера.
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: post-deployment
spec:
replicas: 1
selector:
matchLabels: # Какие поды поддерживать в нужном количестве
app: post
template:
metadata:
name: post
labels:
app: post
spec:
containers:
- image: asomir/post # Метка, чтобы Replication controller нашел под
name: post
• Создайте папку kubernetes в корне репозитория
• Сохраните файл post-deployment.yml в папку kubernetes
P.S. Эту папку и файлы в ней в дальнейшем мы будем развивать (пока это не рабочие экземпляры)
В этом руководстве предполагается, что у нас есть доступ к Облачной платформе Google. Хотя GCP используется для базовых требований к инфраструктуре, уроки, извлеченные в этом руководстве, могут быть применены к другим платформам.
gcloud version
gcloud init
gcloud config set compute/region us-west1
# По умолчанию используем зону us-west1-c
gcloud config set compute/zone us-west1-c
Утилиты командной строки cfssl и cfssljson будут использоваться для предоставления инфраструктуры PKI и создания сертификатов TLS.
Загружаем и устанавливаем cfssl и cfssljson из репозитория cfssl:
wget -q --show-progress --https-only --timestamping \
https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 \
https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
chmod +x cfssl_linux-amd64 cfssljson_linux-amd64
sudo mv cfssl_linux-amd64 /usr/local/bin/cfssl
sudo mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
Проверяем:
cfssl version
output
Version: 1.2.0
Revision: dev
Runtime: go1.6
Утилита командной строки kubectl используется для взаимодействия с сервером API Kubernetes. Загружаем и устанавливаем kubectl из официальных релизов:
wget https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kubectl
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
kubectl version --client
output
Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.0", GitCommit:"fc32d2f3698e36b93322a3465f63a14e9f0eaead", GitTreeState:"clean", BuildDate:"2018-03-26T16:55:54Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
Kubernetes требуется набор машин для размещения системы управления Kubernetes и рабочих узлов, где в конечном итоге выполняются контейнеры. В этой лаборатории мы предоставим вычислительные ресурсы, необходимые для работы безопасного и высокодоступного кластера Kubernetes в одной вычислительной зоне.
Сетевая модель Kubernetes представляет собой сеть, в которой контейнеры и узлы могут взаимодействовать друг с другом. В случаях, когда это нежелательно, сетевые политики могут ограничивать взаимодействие групп контейнеров между собой и внешними конечными точками сети.
В этом разделе будет настроена выделенная сеть виртуального частного облака (VPC) для размещения кластера Kubernetes.
gcloud compute networks create kubernetes-the-hard-way --subnet-mode custom
output
Created [https://www.googleapis.com/compute/v1/projects/docker-194414/global/networks/kubernetes-the-hard-way].
NAME SUBNET_MODE BGP_ROUTING_MODE IPV4_RANGE GATEWAY_IPV4
kubernetes-the-hard-way CUSTOM REGIONAL
Instances on this network will not be reachable until firewall rules
are created. As an example, you can allow all internal traffic between
instances as well as SSH, RDP, and ICMP by running:
$ gcloud compute firewall-rules create <FIREWALL_NAME> --network kubernetes-the-hard-way --allow tcp,udp,icmp --source-ranges <IP_RANGE>
$ gcloud compute firewall-rules create <FIREWALL_NAME> --network kubernetes-the-hard-way --allow tcp:22,tcp:3389,icmp
Подсеть должна быть снабжена диапазоном IP-адресов, достаточно большим для назначения частного IP-адреса каждому узлу кластера Kubernetes
gcloud compute networks subnets create kubernetes \
--network kubernetes-the-hard-way \
--range 10.240.0.0/24
output
Created [https://www.googleapis.com/compute/v1/projects/docker-194414/regions/europe-west1/subnetworks/kubernetes].
NAME REGION NETWORK RANGE
kubernetes europe-west1 kubernetes-the-hard-way 10.240.0.0/24
gcloud compute firewall-rules create kubernetes-the-hard-way-allow-internal \
--allow tcp,udp,icmp \
--network kubernetes-the-hard-way \
--source-ranges 10.240.0.0/24,10.200.0.0/16
output
Creating firewall...-Created [https://www.googleapis.com/compute/v1/projects/docker-194414/global/firewalls/kubernetes-the-hard-way-allow-internal].
Creating firewall...done.
NAME NETWORK DIRECTION PRIORITY ALLOW DENY
kubernetes-the-hard-way-allow-internal kubernetes-the-hard-way INGRESS 1000 tcp,udp,icmp
gcloud compute firewall-rules create kubernetes-the-hard-way-allow-external \
--allow tcp:22,tcp:6443,icmp \
--network kubernetes-the-hard-way \
--source-ranges 0.0.0.0/0
output
Creating firewall.../Created [https://www.googleapis.com/compute/v1/projects/docker-194414/global/firewalls/kubernetes-the-hard-way-allow-external].
Creating firewall...done.
NAME NETWORK DIRECTION PRIORITY ALLOW DENY
kubernetes-the-hard-way-allow-external kubernetes-the-hard-way INGRESS 1000 tcp:22,tcp:6443,icmp
Для публикации серверов API Kubernetes для удаленных клиентов будет использоваться внешний балансировщик нагрузки https://cloud.google.com/compute/docs/load-balancing/network/
gcloud compute firewall-rules list --filter="network:kubernetes-the-hard-way"
output
NAME NETWORK DIRECTION PRIORITY ALLOW DENY
kubernetes-the-hard-way-allow-external kubernetes-the-hard-way INGRESS 1000 tcp:22,tcp:6443,icmp
kubernetes-the-hard-way-allow-internal kubernetes-the-hard-way INGRESS 1000 tcp,udp,icmp
Выделяем статический IP-адрес, который будет подключен к внешнему балансировщику нагрузки, выходящему на серверы API Kubernetes:
gcloud compute addresses create kubernetes-the-hard-way \
--region $(gcloud config get-value compute/region)
output
ERROR: (gcloud.compute.addresses.create) Could not fetch resource:
- Quota 'STATIC_ADDRESSES' exceeded. Limit: 1.0 in region europe-west1.
Количество внешних IP для нашего типа аккаунта ограничено 1 штукой. Поэтому лезем в GCP и удаляем айпи, которые мы биндили в предыдущих лабораторках.
output
Created [https://www.googleapis.com/compute/v1/projects/docker-194414/regions/europe-west1/addresses/kubernetes-the-hard-way].
gcloud compute addresses list --filter="name=('kubernetes-the-hard-way')"
output
NAME REGION ADDRESS STATUS
kubernetes-the-hard-way europe-west1 35.187.10.59 RESERVED
Все инстансы в этой лаборатории будут созданы с использованием Ubuntu Server 16.04, который имеет хорошую поддержку для среды выполнения cri-containerd. Каждому инстансу будет предоставлен фиксированный частный IP-адрес, чтобы упростить процесс начальной загрузки Kubernetes.
for i in 0 1 2; do
gcloud compute instances create controller-${i} \
--async \
--boot-disk-size 200GB \
--can-ip-forward \
--image-family ubuntu-1604-lts \
--image-project ubuntu-os-cloud \
--machine-type n1-standard-1 \
--private-network-ip 10.240.0.1${i} \
--scopes compute-rw,storage-ro,service-management,service-control,logging-write,monitoring \
--subnet kubernetes \
--tags kubernetes-the-hard-way,controller
done
output
Instance creation in progress for [controller-0]: https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/operations/operation-1522503320869-568b56d9c1888-56a1a189-ceb18ad6
Use [gcloud compute operations describe URI] command to check the status of the operation(s).
Instance creation in progress for [controller-1]: https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/operations/operation-1522503323503-568b56dc4499a-c5e8290b-1790c867
Use [gcloud compute operations describe URI] command to check the status of the operation(s).
Instance creation in progress for [controller-2]: https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/operations/operation-1522503326381-568b56df033c9-7db19b94-84ad790d
Use [gcloud compute operations describe URI] command to check the status of the operation(s).
Каждому инстансу требуется подсеть pod-а расположенного в диапазоне CIDR кластера Kubernetes. Распределение подсети pod-а будет использоваться для конфигурирования сетей контейнеров в последующих упражнениях. Метаданные экземпляра pod-cidr будут использоваться для выделения подсетей pod-ов для создания инстансов во время выполнения
Диапазон CIDR кластера Kubernetes определяется флажком -cluster-cidr диспетчера контроллера. В этом уроке диапазон CIDR кластера будет установлен в 10.200.0.0/16, который поддерживает 254 подсети.
for i in 0 1 2; do
gcloud compute instances create worker-${i} \
--async \
--boot-disk-size 200GB \
--can-ip-forward \
--image-family ubuntu-1604-lts \
--image-project ubuntu-os-cloud \
--machine-type n1-standard-1 \
--metadata pod-cidr=10.200.${i}.0/24 \
--private-network-ip 10.240.0.2${i} \
--scopes compute-rw,storage-ro,service-management,service-control,logging-write,monitoring \
--subnet kubernetes \
--tags kubernetes-the-hard-way,worker
done
output
Instance creation in progress for [worker-0]: https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/operations/operation-1522504024458-568b5978c0310-0c1f951e-cd142a49
Use [gcloud compute operations describe URI] command to check the status of the operation(s).
Instance creation in progress for [worker-1]: https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/operations/operation-1522504026910-568b597b16d30-2bf96c1f-12568b9d
Use [gcloud compute operations describe URI] command to check the status of the operation(s).
Instance creation in progress for [worker-2]: https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/operations/operation-1522504029482-568b597d8ac13-1e2e4a88-ec8784f3
Use [gcloud compute operations describe URI] command to check the status of the operation(s).
gcloud compute instances list
output
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
controller-0 europe-west1-c n1-standard-1 10.240.0.10 35.189.220.78 RUNNING
controller-1 europe-west1-c n1-standard-1 10.240.0.11 35.189.217.138 RUNNING
controller-2 europe-west1-c n1-standard-1 10.240.0.12 35.187.86.197 RUNNING
worker-0 europe-west1-c n1-standard-1 10.240.0.20 35.187.59.135 RUNNING
worker-1 europe-west1-c n1-standard-1 10.240.0.21 35.187.103.138 RUNNING
worker-2 europe-west1-c n1-standard-1 10.240.0.22 35.187.80.110 RUNNING
В этой лаборатории мы развернём инфраструктуру PKI с помощью инструментария CloudFlare PKI - cfssl, - затем используем его для загрузки центра сертификации и создания сертификатов TLS для следующих компонентов: etcd, kube-apiserver, kubelet и kube-proxy.
В этой секции мы развернём центр Сертификации, который будет использоваться для создания добавочных TLS сертификатов.
cat > ca-config.json <<EOF
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"kubernetes": {
"usages": ["signing", "key encipherment", "server auth", "client auth"],
"expiry": "8760h"
}
}
}
}
EOF
cat > ca-csr.json <<EOF
{
"CN": "Kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "Portland",
"O": "Kubernetes",
"OU": "CA",
"ST": "Oregon"
}
]
}
EOF
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
output
2018/03/31 17:20:29 [INFO] generating a new CA key and certificate from CSR
2018/03/31 17:20:29 [INFO] generate received request
2018/03/31 17:20:29 [INFO] received CSR
2018/03/31 17:20:29 [INFO] generating key: rsa-2048
2018/03/31 17:20:29 [INFO] encoded CSR
2018/03/31 17:20:29 [INFO] signed certificate with serial number 517291740959154520333108998637393139077620375351
Result: ca-key.pem ca.pem
В этой секции мы создадим клиентские и серверные сертификаты для каждого компонента Kubernetes и клиентский сертификат для пользователя admin Kubernetes
cat > admin-csr.json <<EOF
{
"CN": "admin",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "Portland",
"O": "system:masters",
"OU": "Kubernetes The Hard Way",
"ST": "Oregon"
}
]
}
EOF
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-profile=kubernetes \
admin-csr.json | cfssljson -bare admin
output
2018/03/31 17:27:23 [INFO] generate received request
2018/03/31 17:27:23 [INFO] received CSR
2018/03/31 17:27:23 [INFO] generating key: rsa-2048
2018/03/31 17:27:24 [INFO] encoded CSR
2018/03/31 17:27:24 [INFO] signed certificate with serial number 220955992656473698065928498566533380880979756528
2018/03/31 17:27:24 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").
Results: admin-key.pem admin.pem
Kubernetes использует специальный режим авторизации, называемый Node Authorizer, который разрешает запросы API, сделанные Kubelets. Чтобы авторизоваться d Node Authorizer, Kubelets должен использовать учетные данные, которые идентифицируются как system:nodes с именем system:node:
В этом разделе мы создадим сертификат для каждого рабочего узла Kubernetes, который удовлетворяет требованиям Node Authorizer.
for instance in worker-0 worker-1 worker-2; do
cat > ${instance}-csr.json <<EOF
{
"CN": "system:node:${instance}",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "Portland",
"O": "system:nodes",
"OU": "Kubernetes The Hard Way",
"ST": "Oregon"
}
]
}
EOF
EXTERNAL_IP=$(gcloud compute instances describe ${instance} \
--format 'value(networkInterfaces[0].accessConfigs[0].natIP)')
INTERNAL_IP=$(gcloud compute instances describe ${instance} \
--format 'value(networkInterfaces[0].networkIP)')
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-hostname=${instance},${EXTERNAL_IP},${INTERNAL_IP} \
-profile=kubernetes \
${instance}-csr.json | cfssljson -bare ${instance}
done
output
2018/03/31 17:37:14 [INFO] generate received request
2018/03/31 17:37:14 [INFO] received CSR
2018/03/31 17:37:14 [INFO] generating key: rsa-2048
2018/03/31 17:37:14 [INFO] encoded CSR
2018/03/31 17:37:14 [INFO] signed certificate with serial number 629311987656347244410150681752434259976665356492
2018/03/31 17:37:16 [INFO] generate received request
2018/03/31 17:37:16 [INFO] received CSR
2018/03/31 17:37:16 [INFO] generating key: rsa-2048
2018/03/31 17:37:17 [INFO] encoded CSR
2018/03/31 17:37:17 [INFO] signed certificate with serial number 80368820250113608803997366531865511194319866127
2018/03/31 17:37:19 [INFO] generate received request
2018/03/31 17:37:19 [INFO] received CSR
2018/03/31 17:37:19 [INFO] generating key: rsa-2048
2018/03/31 17:37:19 [INFO] encoded CSR
2018/03/31 17:37:19 [INFO] signed certificate with serial number 239596205710900482054871683117156784809169780486
Results:
worker-0-key.pem
worker-0.pem
worker-1-key.pem
worker-1.pem
worker-2-key.pem
worker-2.pem
cat > kube-proxy-csr.json <<EOF
{
"CN": "system:kube-proxy",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "Portland",
"O": "system:node-proxier",
"OU": "Kubernetes The Hard Way",
"ST": "Oregon"
}
]
}
EOF
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-profile=kubernetes \
kube-proxy-csr.json | cfssljson -bare kube-proxy
output
2018/03/31 17:40:36 [INFO] generate received request
2018/03/31 17:40:36 [INFO] received CSR
2018/03/31 17:40:36 [INFO] generating key: rsa-2048
2018/03/31 17:40:38 [INFO] encoded CSR
2018/03/31 17:40:38 [INFO] signed certificate with serial number 218230184839130505543500517665415013806362365235
2018/03/31 17:40:38 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").
Results: kube-proxy-key.pem kube-proxy.pem
Статический IP адрес kubernetes-the-hard-way будет включён в список альтернативных имён для сертификата Kubernetes API сервера Это гарантирует, что сертификат может быть проверен удаленными клиентами.
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
--region $(gcloud config get-value compute/region) \
--format 'value(address)')
cat > kubernetes-csr.json <<EOF
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "Portland",
"O": "Kubernetes",
"OU": "Kubernetes The Hard Way",
"ST": "Oregon"
}
]
}
EOF
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-hostname=10.32.0.1,10.240.0.10,10.240.0.11,10.240.0.12,${KUBERNETES_PUBLIC_ADDRESS},127.0.0.1,kubernetes.default \
-profile=kubernetes \
kubernetes-csr.json | cfssljson -bare kubernetes
output
2018/03/31 17:49:13 [INFO] generate received request
2018/03/31 17:49:13 [INFO] received CSR
2018/03/31 17:49:13 [INFO] generating key: rsa-2048
2018/03/31 17:49:13 [INFO] encoded CSR
2018/03/31 17:49:13 [INFO] signed certificate with serial number 2407003850554333109408762868611535053182373882
Results: kubernetes-key.pem kubernetes.pem
for instance in worker-0 worker-1 worker-2; do
gcloud compute scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/
done
output
WARNING: The public SSH key file for gcloud does not exist.
WARNING: The private SSH key file for gcloud does not exist.
WARNING: You do not have an SSH key for gcloud.
WARNING: SSH keygen will be executed to generate a key.
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/asomir/.ssh/google_compute_engine.
Your public key has been saved in /home/asomir/.ssh/google_compute_engine.pub.
The key fingerprint is:
SHA256:CW16CJYLpYSxLHi4CHN+yz0eV7CJL8uhgN1D5s8tLrI asomir@asomir-ubuntu
The key's randomart image is:
+---[RSA 2048]----+
|.o. . |
|++ o . . |
|B.= + . + |
|+* o o * = |
|o . = + S . |
| o B o o . |
|. o * * o |
| ...B.O |
| Eo.oB.. |
+----[SHA256]-----+
Updating project ssh metadata...|Updated [https://www.googleapis.com/compute/v1/projects/docker-194414].
Updating project ssh metadata...done.
Waiting for SSH key to propagate.
Warning: Permanently added 'compute.8850945318776352695' (ECDSA) to the list of known hosts.
ca.pem 100% 1367 1.3KB/s 00:00
worker-0-key.pem 100% 1679 1.6KB/s 00:00
worker-0.pem 100% 1493 1.5KB/s 00:00
Warning: Permanently added 'compute.3991824877216739252' (ECDSA) to the list of known hosts.
ca.pem 100% 1367 1.3KB/s 00:00
worker-1-key.pem 100% 1675 1.6KB/s 00:00
worker-1.pem 100% 1493 1.5KB/s 00:00
Warning: Permanently added 'compute.23582907428908978' (ECDSA) to the list of known hosts.
ca.pem 100% 1367 1.3KB/s 00:00
worker-2-key.pem 100% 1675 1.6KB/s 00:00
worker-2.pem 100% 1493 1.5KB/s 00:00
for instance in controller-0 controller-1 controller-2; do
gcloud compute scp ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem ${instance}:~/
done
output
Warning: Permanently added 'compute.1748940011183681654' (ECDSA) to the list of known hosts.
ca.pem 100% 1367 1.3KB/s 00:00
ca-key.pem 100% 1675 1.6KB/s 00:00
kubernetes-key.pem 100% 1679 1.6KB/s 00:00
kubernetes.pem 100% 1521 1.5KB/s 00:00
Warning: Permanently added 'compute.1910890222754869364' (ECDSA) to the list of known hosts.
ca.pem 100% 1367 1.3KB/s 00:00
ca-key.pem 100% 1675 1.6KB/s 00:00
kubernetes-key.pem 100% 1679 1.6KB/s 00:00
kubernetes.pem 100% 1521 1.5KB/s 00:00
Warning: Permanently added 'compute.1826529724962320497' (ECDSA) to the list of known hosts.
ca.pem 100% 1367 1.3KB/s 00:00
ca-key.pem 100% 1675 1.6KB/s 00:00
kubernetes-key.pem 100% 1679 1.6KB/s 00:00
kubernetes.pem 100% 1521 1.5KB/s 00:00
В этой лабе мы сгенерируем Kubernetes configuration files, также известные как kubeconfigs, которые позволяют клиентам Kubernetes находить и проверять подлинность на серверах API Kubernetes
В этом разделе мы создадим файлы kubeconfig для клиентов kubelet и kube-proxy.
scheduler и controller manager получают доступ к серверу API Kubernetes локально через небезопасный порт API, который не требует аутентификации.
Небезопасный порт сервера Kubernetes API доступен только для локального доступа.
Для каждого kubeconfig требуется подключение к серверу API Kubernetes. Для обеспечения высокой доступности будет использоваться IP-адрес, назначенный внешнему балансировщику нагрузки, выходящему на серверы API Kubernetes.
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
--region $(gcloud config get-value compute/region) \
--format 'value(address)')
При создании файлов kubeconfig для Kubelets должен использоваться сертификат клиента, соответствующий имени узла Kubelet. Это обеспечит правильное разрешение Kubelets авторизованным узлом Kubernetes Node
for instance in worker-0 worker-1 worker-2; do
kubectl config set-cluster kubernetes-the-hard-way \
--certificate-authority=ca.pem \
--embed-certs=true \
--server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
--kubeconfig=${instance}.kubeconfig
kubectl config set-credentials system:node:${instance} \
--client-certificate=${instance}.pem \
--client-key=${instance}-key.pem \
--embed-certs=true \
--kubeconfig=${instance}.kubeconfig
kubectl config set-context default \
--cluster=kubernetes-the-hard-way \
--user=system:node:${instance} \
--kubeconfig=${instance}.kubeconfig
kubectl config use-context default --kubeconfig=${instance}.kubeconfig
done
output
Cluster "kubernetes-the-hard-way" set.
User "system:node:worker-0" set.
Context "default" created.
Switched to context "default".
Cluster "kubernetes-the-hard-way" set.
User "system:node:worker-1" set.
Context "default" created.
Switched to context "default".
Cluster "kubernetes-the-hard-way" set.
User "system:node:worker-2" set.
Context "default" created.
Switched to context "default".
Results: worker-0.kubeconfig worker-1.kubeconfig worker-2.kubeconfig
kubectl config set-cluster kubernetes-the-hard-way \
--certificate-authority=ca.pem \
--embed-certs=true \
--server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \
--kubeconfig=kube-proxy.kubeconfig
output
Cluster "kubernetes-the-hard-way" set.
kubectl config set-credentials kube-proxy \
--client-certificate=kube-proxy.pem \
--client-key=kube-proxy-key.pem \
--embed-certs=true \
--kubeconfig=kube-proxy.kubeconfig
output
User "kube-proxy" set.
kubectl config set-context default \
--cluster=kubernetes-the-hard-way \
--user=kube-proxy \
--kubeconfig=kube-proxy.kubeconfig
output
Context "default" created.
kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig
output
Switched to context "default".
for instance in worker-0 worker-1 worker-2; do
gcloud compute scp ${instance}.kubeconfig kube-proxy.kubeconfig ${instance}:~/
done
output
worker-0.kubeconfig 100% 6450 6.3KB/s 00:00
kube-proxy.kubeconfig 100% 6374 6.2KB/s 00:00
worker-1.kubeconfig 100% 6446 6.3KB/s 00:00
kube-proxy.kubeconfig 100% 6374 6.2KB/s 00:00
worker-2.kubeconfig 100% 6446 6.3KB/s 00:00
kube-proxy.kubeconfig 100% 6374 6.2KB/s 00:00
Kubernetes хранит множество данных, включая состояние кластера, конфигурации приложений и секреты. Kubernetes поддерживает возможность шифрования данных кластера в покое.
В этой секции мы создадим ключ шифрования и конфигурацию шифрования, подходящую для шифрования Kubernetes Secrets.
ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
cat > encryption-config.yaml <<EOF
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: ${ENCRYPTION_KEY}
- identity: {}
EOF
for instance in controller-0 controller-1 controller-2; do
gcloud compute scp encryption-config.yaml ${instance}:~/
done
output
encryption-config.yaml 100% 240 0.2KB/s 00:00
encryption-config.yaml 100% 240 0.2KB/s 00:00
encryption-config.yaml 100% 240 0.2KB/s 00:00
Компоненты Kubernetes не имеют состояния и сохраняют состояние кластера в etcd. В этой секции мы загрузим кластер с тремя узлами и т.д. и настроим его для обеспечения высокой доступности и безопасного удаленного доступа.
Команды в этой лаборатории должны запускаться на каждом экземпляре контроллера: controller-0, controller-1, и controller-2.
gcloud compute ssh controller-0
https://github.com/coreos/etcd
wget -q --show-progress --https-only --timestamping \
"https://github.com/coreos/etcd/releases/download/v3.2.11/etcd-v3.2.11-linux-amd64.tar.gz"
tar -xvf etcd-v3.2.11-linux-amd64.tar.gz
sudo mv etcd-v3.2.11-linux-amd64/etcd* /usr/local/bin/
sudo mkdir -p /etc/etcd /var/lib/etcd
sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/
5. Внутренний IP-адрес экземпляра будет использоваться для обслуживания клиентских запросов и обмена данными с etcd cluster peers.
INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip)
6. Каждый член etcd должен иметь уникальное имя в кластере etcd. Зададим имя etcd для соответствия имени хоста текущего инстанса:
ETCD_NAME=$(hostname -s)
cat > etcd.service <<EOF
[Unit]
Description=etcd
Documentation=https://github.com/coreos
[Service]
ExecStart=/usr/local/bin/etcd \\
--name ${ETCD_NAME} \\
--cert-file=/etc/etcd/kubernetes.pem \\
--key-file=/etc/etcd/kubernetes-key.pem \\
--peer-cert-file=/etc/etcd/kubernetes.pem \\
--peer-key-file=/etc/etcd/kubernetes-key.pem \\
--trusted-ca-file=/etc/etcd/ca.pem \\
--peer-trusted-ca-file=/etc/etcd/ca.pem \\
--peer-client-cert-auth \\
--client-cert-auth \\
--initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\
--listen-peer-urls https://${INTERNAL_IP}:2380 \\
--listen-client-urls https://${INTERNAL_IP}:2379,http://127.0.0.1:2379 \\
--advertise-client-urls https://${INTERNAL_IP}:2379 \\
--initial-cluster-token etcd-cluster-0 \\
--initial-cluster controller-0=https://10.240.0.10:2380,controller-1=https://10.240.0.11:2380,controller-2=https://10.240.0.12:2380 \\
--initial-cluster-state new \\
--data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo mv etcd.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable etcd
output
Created symlink from /etc/systemd/system/multi-user.target.wants/etcd.service to /etc/systemd/system/etcd.service.
sudo systemctl start etcd
9. Не забываем запустить приведенные выше команды 1-9 на каждом узле контроллера: controller-0, controller-1 и controller-2.
gcloud compute ssh controller-1
gcloud compute ssh controller-2
ETCDCTL_API=3 etcdctl member list
output
3a57933972cb5131, started, controller-2, https://10.240.0.12:2380, https://10.240.0.12:2379
f98dc20bce6225a0, started, controller-0, https://10.240.0.10:2380, https://10.240.0.10:2379
ffed16798470cab5, started, controller-1, https://10.240.0.11:2380, https://10.240.0.11:2379
В этой секции вы загрузите слой управления Kubernetes на три инстанса и настроите его для обеспечения высокой доступности. Вы также создадите внешний балансировщик нагрузки, который предоставляет серверу API Kubernetes для удаленных клиентов. На каждом узле будут установлены следующие компоненты: Kubernetes API Server, Scheduler и Controller Manager.
Команды в этой секции необходимо запускать на всех инстансах по очереди: controller-0, controller-1 и controller-2.
gcloud compute ssh controller-0
wget -q --show-progress --https-only --timestamping \
"https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kube-apiserver" \
"https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kube-controller-manager" \
"https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kube-scheduler" \
"https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kubectl"
chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/
sudo mkdir -p /var/lib/kubernetes/
sudo mv ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem encryption-config.yaml /var/lib/kubernetes/
5. Внутренний IP-адрес инстанса будет использоваться для обращения сервера API к членам кластера. Получим внутренний IP-адрес для текущего инстанса:
INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip)
cat > kube-apiserver.service <<EOF
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes
[Service]
ExecStart=/usr/local/bin/kube-apiserver \\
--admission-control=Initializers,NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
--advertise-address=${INTERNAL_IP} \\
--allow-privileged=true \\
--apiserver-count=3 \\
--audit-log-maxage=30 \\
--audit-log-maxbackup=3 \\
--audit-log-maxsize=100 \\
--audit-log-path=/var/log/audit.log \\
--authorization-mode=Node,RBAC \\
--bind-address=0.0.0.0 \\
--client-ca-file=/var/lib/kubernetes/ca.pem \\
--enable-swagger-ui=true \\
--etcd-cafile=/var/lib/kubernetes/ca.pem \\
--etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
--etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
--etcd-servers=https://10.240.0.10:2379,https://10.240.0.11:2379,https://10.240.0.12:2379 \\
--event-ttl=1h \\
--experimental-encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
--insecure-bind-address=127.0.0.1 \\
--kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
--kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
--kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
--kubelet-https=true \\
--runtime-config=api/all \\
--service-account-key-file=/var/lib/kubernetes/ca-key.pem \\
--service-cluster-ip-range=10.32.0.0/24 \\
--service-node-port-range=30000-32767 \\
--tls-ca-file=/var/lib/kubernetes/ca.pem \\
--tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
--tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
cat > kube-controller-manager.service <<EOF
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes
[Service]
ExecStart=/usr/local/bin/kube-controller-manager \\
--address=0.0.0.0 \\
--cluster-cidr=10.200.0.0/16 \\
--cluster-name=kubernetes \\
--cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\
--cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\
--leader-elect=true \\
--master=http://127.0.0.1:8080 \\
--root-ca-file=/var/lib/kubernetes/ca.pem \\
--service-account-private-key-file=/var/lib/kubernetes/ca-key.pem \\
--service-cluster-ip-range=10.32.0.0/24 \\
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
cat > kube-scheduler.service <<EOF
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes
[Service]
ExecStart=/usr/local/bin/kube-scheduler \\
--leader-elect=true \\
--master=http://127.0.0.1:8080 \\
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo mv kube-apiserver.service kube-scheduler.service kube-controller-manager.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl start kube-apiserver kube-controller-manager kube-scheduler
output
Created symlink from /etc/systemd/system/multi-user.target.wants/kube-apiserver.service to /etc/systemd/system/kube-apiserver.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/kube-controller-manager.service to /etc/systemd/system/kube-controller-manager.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/kube-scheduler.service to /etc/systemd/system/kube-scheduler.service.
kubectl get componentstatuses
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-1 Healthy {"health": "true"}
etcd-2 Healthy {"health": "true"}
etcd-0 Healthy {"health": "true"}
11. Не забываем всё то же самое (1-10) выполнить на всех контроллерах нод: controller-0, controller-1 и controller-2.
В этом разделе мы будем настраивать разрешения RBAC, чтобы позволить серверу API Kubernetes обращаться к API-интерфейсу Kubelet на каждом воркере. Доступ к API Kubelet необходим для получения метрик, журналов и выполнения команд в контейнерах.
Этот учебник устанавливает флаг Kubelet -authorization-mode в Webhook. Режим Webhook использует API SubjectAccessReview для определения авторизации.
gcloud compute ssh controller-0
1. Создаём system:kube-apiserver-to-kubelet ClusterRole с разрешениями доступа к API Kubelet и выполнением большинства обычных задач, связанных с управлением подами:
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:kube-apiserver-to-kubelet
rules:
- apiGroups:
- ""
resources:
- nodes/proxy
- nodes/stats
- nodes/log
- nodes/spec
- nodes/metrics
verbs:
- "*"
EOF
output
clusterrole "system:kube-apiserver-to-kubelet" created
Сервер API Kubernetes аутентифицируется на Kubelet как пользователь kubernetes, используя сертификат клиента, как определено флажком --kubelet-client-certificate.
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: system:kube-apiserver
namespace: ""
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kube-apiserver-to-kubelet
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: kubernetes
EOF
output
clusterrolebinding "system:kube-apiserver" created
В этом разделе мы раскатаем внешний балансировщик нагрузки перед серверами API Kubernetes. К результирующему балансировщику нагрузки присоединяется статический IP-адрес kubernetes-the-hard-way.
Примечание: у созданных инстансов нет разрешений на выполнение команд этого раздела, поэтому команды выполняются с компьютера, с которого проводилось создание инстансов
gcloud compute target-pools create kubernetes-target-pool
output
Did you mean region [europe-west1] for target pool:
[kubernetes-target-pool] (Y/n)? y
Created [https://www.googleapis.com/compute/v1/projects/docker-194414/regions/europe-west1/targetPools/kubernetes-target-pool].
NAME REGION SESSION_AFFINITY BACKUP HEALTH_CHECKS
kubernetes-target-pool europe-west1 NONE
gcloud compute target-pools add-instances kubernetes-target-pool \
--instances controller-0,controller-1,controller-2
output
Did you mean zone [europe-west1-c] for instance: [controller-0,
controller-1, controller-2] (Y/n)? y
Updated [https://www.googleapis.com/compute/v1/projects/docker-194414/regions/europe-west1/targetPools/kubernetes-target-pool].
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
--region $(gcloud config get-value compute/region) \
--format 'value(name)')
gcloud compute forwarding-rules create kubernetes-forwarding-rule \
--address ${KUBERNETES_PUBLIC_ADDRESS} \
--ports 6443 \
--region $(gcloud config get-value compute/region) \
--target-pool kubernetes-target-pool
output
Created [https://www.googleapis.com/compute/v1/projects/docker-194414/regions/europe-west1/forwardingRules/kubernetes-forwarding-rule].
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
--region $(gcloud config get-value compute/region) \
--format 'value(address)')
curl --cacert ca.pem https://${KUBERNETES_PUBLIC_ADDRESS}:6443/version
output
{
"major": "1",
"minor": "9",
"gitVersion": "v1.9.0",
"gitCommit": "925c127ec6b946659ad0fd596fa959be43f0cc05",
"gitTreeState": "clean",
"buildDate": "2017-12-15T20:55:30Z",
"goVersion": "go1.9.2",
"compiler": "gc",
"platform": "linux/amd64"
}
В этой секции мы развернём три воркер ноды Кубернетиса. Следующие компоненты должны быть установлены на каждой ноде:
-
runc, - является инструментом CLI для генерации и запуска контейнеров в соответствии со спецификацией OCI. https://github.com/opencontainers/runc
-
container networking plugins, - проект, состоит из спецификации и библиотек для написания плагинов для настройки сетевых интерфейсов в контейнерах Linux, а также ряда поддерживаемых плагинов. CNI относится только к сетевому подключению контейнеров и удалению выделенных ресурсов при удалении контейнера. CNI имеет широкий диапазон поддержки, и спецификация проста в реализации. https://github.com/containernetworking/cni
-
cri-containerd, https://github.com/containerd/cri
-
kubelet, https://kubernetes.io/docs/reference/generated/kubelet/ - Кубелет является основным «агентом узла», который выполняется на каждом узле. Кубе работает с точки зрения PodSpec. PodSpec - объект YAML или JSON, который описывает модуль. Kubelet принимает набор PodSpec, которые предоставляются через различные механизмы (в первую очередь через apiserver) и гарантирует, что контейнеры, описанные в этих PodSpec, работают и здоровы. Кубелет не управляет контейнерами, которые не были созданы Кубернетом. Помимо того, что из PodSpec от apirusver существует три способа, чтобы контейнерный манифест мог быть предоставлен Kubelet.
-
Файл: Путь передан как флаг в командной строке. Файлы под этим путем будут периодически проверяться на наличие обновлений. Период мониторинга по умолчанию равен 20 с и настраивается с помощью флага.
-
Конечная точка HTTP: конечная точка HTTP передается как параметр в командной строке. Эта конечная точка проверяется каждые 20 секунд (также настраивается с помощью флага).
-
HTTP-сервер: кубелет также может прослушивать HTTP и отвечать на простой API (underspec'd в настоящее время), чтобы отправить новый манифест.
-
-
kube-proxy. - https://kubernetes.io/docs/concepts/cluster-administration/proxies/
Команды в этой секции должны быть выполнены на всех воркерах: worker-0, worker-1 и worker-2. Войдите в каждый рабочий экземпляр, используя команду gcloud:
gcloud compute ssh worker-0
sudo apt-get -y install socat
Бинарный файл socat поддерживает поддержку команды kubectl port-forward.
wget -q --show-progress --https-only --timestamping \
https://github.com/containernetworking/plugins/releases/download/v0.6.0/cni-plugins-amd64-v0.6.0.tgz \
https://github.com/containerd/cri-containerd/releases/download/v1.0.0-beta.1/cri-containerd-1.0.0-beta.1.linux-amd64.tar.gz \
https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kubectl \
https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kube-proxy \
https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/linux/amd64/kubelet
sudo mkdir -p \
/etc/cni/net.d \
/opt/cni/bin \
/var/lib/kubelet \
/var/lib/kube-proxy \
/var/lib/kubernetes \
/var/run/kubernetes
sudo tar -xvf cni-plugins-amd64-v0.6.0.tgz -C /opt/cni/bin/
sudo tar -xvf cri-containerd-1.0.0-beta.1.linux-amd64.tar.gz -C /
chmod +x kubectl kube-proxy kubelet
sudo mv kubectl kube-proxy kubelet /usr/local/bin/
POD_CIDR=$(curl -s -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/attributes/pod-cidr)
cat > 10-bridge.conf <<EOF
{
"cniVersion": "0.3.1",
"name": "bridge",
"type": "bridge",
"bridge": "cnio0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"ranges": [
[{"subnet": "${POD_CIDR}"}]
],
"routes": [{"dst": "0.0.0.0/0"}]
}
}
EOF
cat > 99-loopback.conf <<EOF
{
"cniVersion": "0.3.1",
"type": "loopback"
}
EOF
sudo mv 10-bridge.conf 99-loopback.conf /etc/cni/net.d/
sudo mv ${HOSTNAME}-key.pem ${HOSTNAME}.pem /var/lib/kubelet/
sudo mv ${HOSTNAME}.kubeconfig /var/lib/kubelet/kubeconfig
sudo mv ca.pem /var/lib/kubernetes/
cat > kubelet.service <<EOF
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=cri-containerd.service
Requires=cri-containerd.service
[Service]
ExecStart=/usr/local/bin/kubelet \\
--allow-privileged=true \\
--anonymous-auth=false \\
--authorization-mode=Webhook \\
--client-ca-file=/var/lib/kubernetes/ca.pem \\
--cloud-provider= \\
--cluster-dns=10.32.0.10 \\
--cluster-domain=cluster.local \\
--container-runtime=remote \\
--container-runtime-endpoint=unix:///var/run/cri-containerd.sock \\
--image-pull-progress-deadline=2m \\
--kubeconfig=/var/lib/kubelet/kubeconfig \\
--network-plugin=cni \\
--pod-cidr=${POD_CIDR} \\
--register-node=true \\
--runtime-request-timeout=15m \\
--tls-cert-file=/var/lib/kubelet/${HOSTNAME}.pem \\
--tls-private-key-file=/var/lib/kubelet/${HOSTNAME}-key.pem \\
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo mv kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig
cat > kube-proxy.service <<EOF
[Unit]
Description=Kubernetes Kube Proxy
Documentation=https://github.com/kubernetes/kubernetes
[Service]
ExecStart=/usr/local/bin/kube-proxy \\
--cluster-cidr=10.200.0.0/16 \\
--kubeconfig=/var/lib/kube-proxy/kubeconfig \\
--proxy-mode=iptables \\
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo mv kubelet.service kube-proxy.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable containerd cri-containerd kubelet kube-proxy
sudo systemctl start containerd cri-containerd kubelet kube-proxy
gcloud compute ssh controller-0
kubectl get nodes
output
NAME STATUS ROLES AGE VERSION
worker-0 Ready <none> 12m v1.9.0
worker-1 Ready <none> 5m v1.9.0
worker-2 Ready <none> 24s v1.9.0
В этой лаборатории мы создадим файл kubeconfig для утилиты командной строки kubectl на основе учетных данных пользователя admin.
Запустите команды в этой лаборатории из того же каталога, который используется для создания сертификатов клиента admin.
Для каждого kubeconfig требуется подключение к серверу API Kubernetes. Для обеспечения высокой доступности будет использоваться IP-адрес, назначенный внешнему балансировщику нагрузки, выходящему на серверы API Kubernetes.
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \
--region $(gcloud config get-value compute/region) \
--format 'value(address)')
kubectl config set-cluster kubernetes-the-hard-way \
--certificate-authority=ca.pem \
--embed-certs=true \
--server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443
kubectl config set-credentials admin \
--client-certificate=admin.pem \
--client-key=admin-key.pem
kubectl config set-context kubernetes-the-hard-way \
--cluster=kubernetes-the-hard-way \
--user=admin
kubectl config use-context kubernetes-the-hard-way
kubectl get componentstatuses
output:
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-2 Healthy {"health": "true"}
etcd-0 Healthy {"health": "true"}
etcd-1 Healthy {"health": "true"}
kubectl get nodes
NAME STATUS ROLES AGE VERSION
worker-0 Ready <none> 26m v1.9.0
worker-1 Ready <none> 19m v1.9.0
worker-2 Ready <none> 14m v1.9.0
Поды, прикреплённые к ноде, получают IP-адрес из диапазона CIDR узла. В этот момент поды не могут взаимодействовать с другими модулями, запущенными на разных узлах из-за отсутствия сетевых маршрутов. https://cloud.google.com/vpc/docs/routes
Существуют и другие способы реализации сетевой модели Kubernetes: https://kubernetes.io/docs/concepts/cluster-administration/networking/#how-to-achieve-this
В этой секции мы соберём информацию, необходимую для создания маршрутов в сети VPC kubernetes-hard-way
for instance in worker-0 worker-1 worker-2; do
gcloud compute instances describe ${instance} \
--format 'value[separator=" "](networkInterfaces[0].networkIP,metadata.items[0].value)'
done
output
10.240.0.20 10.200.0.0/24
10.240.0.21 10.200.1.0/24
10.240.0.22 10.200.2.0/24
for i in 0 1 2; do
gcloud compute routes create kubernetes-route-10-200-${i}-0-24 \
--network kubernetes-the-hard-way \
--next-hop-address 10.240.0.2${i} \
--destination-range 10.200.${i}.0/24
done
output
Created [https://www.googleapis.com/compute/v1/projects/docker-194414/global/routes/kubernetes-route-10-200-0-0-24].
NAME NETWORK DEST_RANGE NEXT_HOP PRIORITY
kubernetes-route-10-200-0-0-24 kubernetes-the-hard-way 10.200.0.0/24 10.240.0.20 1000
Created [https://www.googleapis.com/compute/v1/projects/docker-194414/global/routes/kubernetes-route-10-200-1-0-24].
NAME NETWORK DEST_RANGE NEXT_HOP PRIORITY
kubernetes-route-10-200-1-0-24 kubernetes-the-hard-way 10.200.1.0/24 10.240.0.21 1000
Created [https://www.googleapis.com/compute/v1/projects/docker-194414/global/routes/kubernetes-route-10-200-2-0-24].
NAME NETWORK DEST_RANGE NEXT_HOP PRIORITY
kubernetes-route-10-200-2-0-24 kubernetes-the-hard-way 10.200.2.0/24 10.240.0.22 1000
gcloud compute routes list --filter "network: kubernetes-the-hard-way"
output
NAME NETWORK DEST_RANGE NEXT_HOP PRIORITY
default-route-da909e003b938066 kubernetes-the-hard-way 0.0.0.0/0 default-internet-gateway 1000
default-route-ecc9970c8775d9fb kubernetes-the-hard-way 10.240.0.0/24 1000
kubernetes-route-10-200-0-0-24 kubernetes-the-hard-way 10.200.0.0/24 10.240.0.20 1000
kubernetes-route-10-200-1-0-24 kubernetes-the-hard-way 10.200.1.0/24 10.240.0.21 1000
kubernetes-route-10-200-2-0-24 kubernetes-the-hard-way 10.200.2.0/24 10.240.0.22 1000
kubectl create -f https://storage.googleapis.com/kubernetes-the-hard-way/kube-dns.yaml
output
service "kube-dns" created
serviceaccount "kube-dns" created
configmap "kube-dns" created
deployment.extensions "kube-dns" created
kubectl get pods -l k8s-app=kube-dns -n kube-system
output
NAME READY STATUS RESTARTS AGE
kube-dns-6c857864fb-dvb5q 3/3 Running 0 1m
kubectl run busybox --image=busybox --command -- sleep 3600
kubectl get pods -l run=busybox
output
NAME READY STATUS RESTARTS AGE
busybox-855686df5d-rz57s 1/1 Running 0 15s
POD_NAME=$(kubectl get pods -l run=busybox -o jsonpath="{.items[0].metadata.name}")
kubectl exec -ti $POD_NAME -- nslookup kubernetes
output
Server: 10.32.0.10
Address 1: 10.32.0.10 kube-dns.kube-system.svc.cluster.local
Name: kubernetes
Address 1: 10.32.0.1 kubernetes.default.svc.cluster.local
В этой лаборатории мы выполним ряд задач, чтобы убедиться, что ваш кластер Kubernetes работает правильно.
В этом разделе вы проверите возможность шифрования секретных данных в покое.
kubectl create secret generic kubernetes-the-hard-way \
--from-literal="mykey=mydata"
gcloud compute ssh controller-0 \
--command "ETCDCTL_API=3 etcdctl get /registry/secrets/default/kubernetes-the-hard-way | hexdump -C"
output
00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret|
00000010 73 2f 64 65 66 61 75 6c 74 2f 6b 75 62 65 72 6e |s/default/kubern|
00000020 65 74 65 73 2d 74 68 65 2d 68 61 72 64 2d 77 61 |etes-the-hard-wa|
00000030 79 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 63 |y.k8s:enc:aescbc|
00000040 3a 76 31 3a 6b 65 79 31 3a 4f 61 3a 97 67 58 6e |:v1:key1:Oa:.gXn|
00000050 ce a7 3c c8 01 7c 5c 45 2c 0b be b1 84 6e 0f 97 |..<..|\E,....n..|
00000060 bd 24 d2 f8 43 91 2d a3 ec ea d5 d8 e9 14 75 73 |.$..C.-.......us|
00000070 2b 8d 1f d1 12 a5 a3 a3 49 6a e3 43 0e 94 f8 fa |+.......Ij.C....|
00000080 5a 1e 88 96 0a d6 22 2b fc 77 6f 7a 62 c6 96 6b |Z....."+.wozb..k|
00000090 0c 90 20 b9 73 72 82 d4 18 01 37 6d 23 de f6 8e |.. .sr....7m#...|
000000a0 51 47 2c af dc 61 18 ec ef 6d 80 5d 71 a5 9f 77 |QG,..a...m.]q..w|
000000b0 8b 02 3c da a6 d1 2d 3d 9a 46 ee 3d f6 77 2a 01 |..<...-=.F.=.w*.|
000000c0 d2 e6 83 e2 2b 84 48 2c ef 45 53 91 6e 65 e7 a4 |....+.H,.ES.ne..|
000000d0 90 a3 0b 7b f9 d4 eb 2f de 14 3a 4a 69 80 33 14 |...{.../..:Ji.3.|
000000e0 7f da 2a 11 92 9d 65 01 ca 0a |..*...e...|
000000ea
Ключ etcd должен иметь префикс k8s: enc: aescbc: v1: key1, который указывает, что поставщик aescbc использовался для шифрования данных ключом ключа key1.
В этом разделе вы сможете проверить возможность создания и управления развертываниями. https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
kubectl run nginx --image=nginx
kubectl get pods -l run=nginx
output
NAME READY STATUS RESTARTS AGE
nginx-8586cf59-2z8qp 1/1 Running 0 58s
В этом разделе вы будете проверять возможность удаленного доступа к приложениям с помощью переадресации портов.
POD_NAME=$(kubectl get pods -l run=nginx -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward $POD_NAME 8080:80
output
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
curl --head http://127.0.0.1:8080
output
HTTP/1.1 200 OK
Server: nginx/1.13.10
Date: Sun, 01 Apr 2018 09:03:39 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 20 Mar 2018 10:03:54 GMT
Connection: keep-alive
ETag: "5ab0dc8a-264"
Accept-Ranges: bytes
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Handling connection for 8080
^C
kubectl logs $POD_NAME
output
127.0.0.1 - - [01/Apr/2018:09:03:39 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.47.0" "-"
kubectl exec -ti $POD_NAME -- nginx -v
output
nginx version: nginx/1.13.10
kubectl expose deployment nginx --port 80 --type NodePort
Тип службы LoadBalancer нельзя использовать, поскольку ваш кластер не настроен на интеграцию с облачным провайдером. Настройка интеграции провайдера облачных вычислений выходит за рамки этого руководства.
NODE_PORT=$(kubectl get svc nginx \
--output=jsonpath='{range .spec.ports[0]}{.nodePort}')
gcloud compute firewall-rules create kubernetes-the-hard-way-allow-nginx-service \
--allow=tcp:${NODE_PORT} \
--network kubernetes-the-hard-way
output
Creating firewall...-Created [https://www.googleapis.com/compute/v1/projects/docker-194414/global/firewalls/kubernetes-the-hard-way-allow-nginx-service].
Creating firewall...done.
NAME NETWORK DIRECTION PRIORITY ALLOW DENY
kubernetes-the-hard-way-allow-nginx-service kubernetes-the-hard-way INGRESS 1000 tcp:31959
EXTERNAL_IP=$(gcloud compute instances describe worker-0 \
--format 'value(networkInterfaces[0].accessConfigs[0].natIP)')
curl -I http://${EXTERNAL_IP}:${NODE_PORT}
output
HTTP/1.1 200 OK
Server: nginx/1.13.10
Date: Sun, 01 Apr 2018 09:17:25 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 20 Mar 2018 10:03:54 GMT
Connection: keep-alive
ETag: "5ab0dc8a-264"
Accept-Ranges: bytes
• Создайте собственные файлы с Deployment манифестами приложений и сохраните в папке kubernetes
• post-deployment.yml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: post-deployment
spec:
replicas: 3
selector:
matchLabels:
app: post
template:
metadata:
name: post
labels:
app: post
spec:
containers:
- image: asomir/post
name: post
• ui-deployment.yml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: ui-deployment
spec:
replicas: 3
selector:
matchLabels:
app: ui
template:
metadata:
name: ui
labels:
app: ui
spec:
containers:
- image: asomir/ui
name: ui
• comment-deployment.yml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: comment-deployment
spec:
replicas: 3
selector:
matchLabels:
app: comment
template:
metadata:
name: comment
labels:
app: comment
spec:
containers:
- image: asomir/comment
name: comment
• mongo-deployment.yml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: mongo-deployment
spec:
replicas: 3
selector:
matchLabels:
app: mongo
template:
metadata:
name: mongo
labels:
app: mongo
spec:
containers:
- image: mongo:3.2
name: mongo
Проверьте, что kubectl apply -f проходит по созданным до этого deployment-ам (ui, post, mongo, comment) и поды запускаются
kubectl get pods
output
NAME READY STATUS RESTARTS AGE
busybox-855686df5d-rz57s 1/1 Running 0 55m
comment-deployment-7ddc57547c-pbb46 1/1 Running 0 2m
comment-deployment-7ddc57547c-r6rzj 1/1 Running 0 2m
comment-deployment-7ddc57547c-wg599 1/1 Running 0 2m
mongo-deployment-74cccfb8-5hvsf 1/1 Running 0 41s
mongo-deployment-74cccfb8-6jdtq 1/1 Running 0 41s
mongo-deployment-74cccfb8-jl8gr 1/1 Running 0 41s
nginx-8586cf59-2z8qp 1/1 Running 0 43m
post-deployment-659858f589-7lrfh 1/1 Running 0 2m
post-deployment-659858f589-7pbgp 1/1 Running 0 2m
post-deployment-659858f589-kss7m 1/1 Running 0 2m
ui-deployment-87cbcc7fc-84dc5 1/1 Running 0 2m
ui-deployment-87cbcc7fc-c7lr8 1/1 Running 0 2m
ui-deployment-87cbcc7fc-f4jzt 1/1 Running 0 2m
gcloud -q compute instances delete \
controller-0 controller-1 controller-2 \
worker-0 worker-1 worker-2
output
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/instances/controller-0].
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/instances/controller-1].
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/instances/controller-2].
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/instances/worker-0].
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/instances/worker-2].
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/zones/europe-west1-c/instances/worker-1].
gcloud -q compute forwarding-rules delete kubernetes-forwarding-rule \
--region $(gcloud config get-value compute/region)
output
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/regions/europe-west1/forwardingRules/kubernetes-forwarding-rule].
gcloud -q compute target-pools delete kubernetes-target-pool
output
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/regions/europe-west1/targetPools/kubernetes-target-pool].
gcloud -q compute addresses delete kubernetes-the-hard-way
output
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/regions/europe-west1/addresses/kubernetes-the-hard-way].
gcloud -q compute firewall-rules delete \
kubernetes-the-hard-way-allow-nginx-service \
kubernetes-the-hard-way-allow-internal \
kubernetes-the-hard-way-allow-external
output
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/global/firewalls/kubernetes-the-hard-way-allow-nginx-service].
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/global/firewalls/kubernetes-the-hard-way-allow-internal].
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/global/firewalls/kubernetes-the-hard-way-allow-external].
gcloud -q compute routes delete \
kubernetes-route-10-200-0-0-24 \
kubernetes-route-10-200-1-0-24 \
kubernetes-route-10-200-2-0-24
output
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/global/routes/kubernetes-route-10-200-0-0-24].
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/global/routes/kubernetes-route-10-200-1-0-24].
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/global/routes/kubernetes-route-10-200-2-0-24].
gcloud -q compute networks subnets delete kubernetes
output
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/regions/europe-west1/subnetworks/kubernetes].
gcloud -q compute networks delete kubernetes-the-hard-way
output
Deleted [https://www.googleapis.com/compute/v1/projects/docker-194414/global/networks/kubernetes-the-hard-way].
Всё. Больше ничего нет. Только Боль. Уныние. Отчаяние. Всем спасибо. Эти двое суток были потрясающими.
Создадим машину master-1, worker-1, worker-2
docker-machine create --driver google \
--google-project docker-194414 \
--google-zone europe-west1-b \
--google-machine-type g1-small \
--google-machine-image $(gcloud compute images list --filter ubuntu-1604-lts --uri) \
master-1
docker-machine create --driver google \
--google-project docker-194414 \
--google-zone europe-west1-b \
--google-machine-type g1-small \
--google-machine-image $(gcloud compute images list --filter ubuntu-1604-lts --uri) \
worker-1
docker-machine create --driver google \
--google-project docker-194414 \
--google-zone europe-west1-b \
--google-machine-type g1-small \
--google-machine-image $(gcloud compute images list --filter ubuntu-1604-lts --uri) \
worker-2
eval $(docker-machine env master-1)
docker swarm init
Swarm initialized: current node (xu9sufx2b8awxw29fkb55o5o8) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-0tg5nnnphxggtdp85nljlrhk4gjbw9kkheuh0oddldua4sva4b-37xt5a2kuc700a7pa49aui812 10.132.0.2:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Выделенная команда позволит добавить только worker-ноды. Также токен для добавления нод можно сгенерировать токен с помощью команды:
docker swarm join-token manager/worker
если на сервере несколько сетевых интерфейсов или сервер находится за NAT, то необходимо указывать флаг -- advertise-addr с конкретным адресом публикации. По-умолчанию это будет <адрес интерфейса>:2377
• Текущая нода переключается в Swarm-режим
• Текущая нода назначается в качестве Лидера менеджеров кластера
• Ноде присваивается хостнейм машины
• Менеджер конфигурируется для прослушивания на порту 2377
• Текущая нода получает статус Active, что означает возможность получать задачи от планировщика
• Запускается внутреннее распределенное хранилище данных Docker для работы оркестратора
• Генерируются токены для присоединения Worker и Manager нод к кластеру
• Генерируется самоподписный корневый (CA) сертификат для Swarm
• Создается Overlay-сеть Ingress для публикации сервисов наружу
docker swarm join --token SWMTKN-1-0tg5nnnphxggtdp85nljlrhk4gjbw9kkheuh0oddldua4sva4b-37xt5a2kuc700a7pa49aui812 10.132.0.2:2377
Подключаемся к master-1 ноде eval $(docker-machine env master-1)
Дальше работать будем только с ней. Команды в рамках Swarm-кластера можно запускать только на Manager-нодах.
docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
xu9sufx2b8awxw29fkb55o5o8 * master-1 Ready Active Leader 18.03.0-ce
wqrt3dr64i3md1i1tej6ek1kd worker-1 Ready Active 18.03.0-ce
dfiw33hegvqyg9czngss6lp88 worker-2 Ready Active 18.03.0-ce
• Сервисы и их зависимости объединяем в Stack
• Stack описываем в формате docker-compose (YML)
$ docker stack deploy/rm/services/ls STACK_NAME
У нас уже есть первичное описание стека для запуска reddit-app в docker-compose.yml. Возьмём с gist:
https://raw.githubusercontent.com/express42/otus-snippets/master/hw-27/docker-compose.yml
$ docker stack deploy --compose-file docker-compose.ym ENV
docker stack deploy --compose-file=<(docker-compose -f docker-compose.yml config 2>/dev/null) DEV
docker stack services DEV
Будете выведена своданая информация по сервисам (не по контейнерам):
ID NAME MODE REPLICAS IMAGE PORTS
0wlndktpczc9 DEV_mongo replicated 1/1 mongo:3.2
isuw3lwbq4p8 DEV_comment replicated 1/1 asomir/comment:latest
rzdpgqnglgso DEV_ui replicated 1/1 asomir/ui:latest *:9292->9292/tcp
v1cku1utk7iq DEV_post replicated 1/1 asomir/post:latest
Ограничения размещения определяются с помощью логических действий со значениями label-ов (медатанных) нод и docker-engine’ов
Обращение к встроенным label’ам нод - node.* Обращение к заданным вручную label’ам нод - node.labels* Обращение к label’ам engine - engine.labels.*
Примеры:
- node.labels.reliability == high
- node.role != manager
- engine.labels.provider == google
docker node update --label-add reliability=high master-1
Swarm не умеет фильтровать вывод по label-ам нод пока что
docker node ls --filter "label=reliability"
docker node ls -q | xargs docker node inspect -f '{{ .ID }} [{{ .Description.Hostname }}]: {{ .Spec.Labels }}'
Предположим, что нода master-1 надежнее и дороже, чем worker-ноды, поэтому поместим туда нашу базу. Определим с помощью placement constraints ограничения размещения
services:
mongo:
image: mongo:${MONGO_VERSION}
deploy:
placement:
constraints:
- node.labels.reliability == high
volumes:
- mongo_data:/data/db
networks:
back_net:
aliases:
- post_db
- comment_db
Основную нагрузку пользователей reddit-app будем принимать на worker-ноды, чтобы не перегружать master с помощью label node.role
version: '3.5'
services:
mongo:
image: mongo:3.2
deploy:
placement:
constraints:
- node.labels.reliability == high
volumes:
- mongo_data:/data/db
networks:
back_net:
aliases:
- post_db
- comment_db
post:
image: ${USER_NAME}/post:latest
deploy:
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
comment:
image: ${USER_NAME}/comment:latest
deploy:
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
ui:
image: ${USER_NAME}/ui:latest
deploy:
placement:
constraints:
- node.role == worker
ports:
- "${UI_PORT}:9292/tcp"
networks:
- front_net
volumes:
mongo_data: {}
networks:
back_net: {}
front_net: {}
docker stack deploy --compose-file=<(docker-compose -f docker-compose.yml config 2>/dev/null) DEV
docker stack ps DEV
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
836bqjmxzknt DEV_post.1 asomir/post:latest worker-1 Running Running 17 minutes ago
vd95hw5ezi1x DEV_mongo.1 mongo:3.2 master-1 Running Running 17 minutes ago
waqt3zjg2sli DEV_comment.1 asomir/comment:latest worker-2 Running Running 17 minutes ago
j7sipv4mhhrv DEV_ui.1 asomir/ui:latest worker-1 Running Running 17 minutes ago
Для горизонтального масшатбирования и большей отказоустойчивости запустим микросервисы в нескольких экземплярах. Существует 2 варианта запуска:
• replicated mode - запустить определенное число задач (default)
• global mode - запустить задачу на каждой ноде
!!! Нельзя заменить replicated mode на global mode ( и обратно) без удаления сервиса
Будем использовать Replicated mode
Запустим приложения ui, post и comment в 2-х экземплярах
version: '3.5'
services:
...
post:
image: ${USER_NAME}/post:latest
deploy:
mode: replicated
replicas: 2
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
comment:
image: ${USER_NAME}/comment:latest
deploy:
mode: replicated
replicas: 2
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
ui:
image: ${USER_NAME}/ui:latest
deploy:
mode: replicated
replicas: 2
placement:
constraints:
- node.role == worker
ports:
- "${UI_PORT}:9292/tcp"
networks:
- front_net
...
docker stack deploy --compose-file=<(docker-compose -f docker-compose.yml config 2>/dev/null) DEV
docker stack services DEV
ID NAME MODE REPLICAS IMAGE PORTS
0wlndktpczc9 DEV_mongo replicated 1/1 mongo:3.2
isuw3lwbq4p8 DEV_comment replicated 2/2 asomir/comment:latest
rzdpgqnglgso DEV_ui replicated 2/2 asomir/ui:latest *:9292->9292/tcp
v1cku1utk7iq DEV_post replicated 2/2 asomir/post:latest
Сервисы распределились равномерно по кластеру (стратегия spread) (проверяем)
docker stack ps DEV
836bqjmxzknt DEV_post.1 asomir/post:latest worker-1 Running Running 26 minutes ago
vd95hw5ezi1x DEV_mongo.1 mongo:3.2 master-1 Running Running 26 minutes ago
waqt3zjg2sli DEV_comment.1 asomir/comment:latest worker-2 Running Running 26 minutes ago
j7sipv4mhhrv DEV_ui.1 asomir/ui:latest worker-1 Running Running 27 minutes ago
yle6wlbgt7ls DEV_comment.2 asomir/comment:latest worker-1 Running Running about a minute ago
o0je63lkz2c3 DEV_ui.2 asomir/ui:latest worker-2 Running Running about a minute ago
06yr6yn151vo DEV_post.2 asomir/post:latest worker-2 Running Running about a minute ago
$ docker service scale DEV_ui=3
ИЛИ
$ docker service update --replicas 3 DEV_ui
$ docker service update --replicas 0 DEV_ui
Для задач мониторинга кластера нам понадобится запускать node_exporter (только в 1-м экземпляре)
node-exporter:
image: prom/node-exporter:v0.15.0
deploy:
mode: global
docker stack deploy --compose-file=<(docker-compose -f docker-compose-monitoring.yml config 2>/dev/null) DEV
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
0db4c3irczni DEV_node-exporter.dfiw33hegvqyg9czngss6lp88 prom/node-exporter:v0.15.2 worker-2 Running Running about a minute ago
tsokokwzs79k DEV_node-exporter.wqrt3dr64i3md1i1tej6ek1kd prom/node-exporter:v0.15.2 worker-1 Running Running about a minute ago
lld3cn9wt71c DEV_node-exporter.xu9sufx2b8awxw29fkb55o5o8 prom/node-exporter:v0.15.2 master-1 Running Running about a minute ago
vajezlu3lwvr DEV_grafana.1 grafana/grafana:5.0.0 master-1 Running Running about a minute ago
qdw3f5p02sx2 DEV_cadvisor.1 google/cadvisor:v0.29.0 worker-2 Running Running about a minute ago
c12ifi1ogvjp DEV_alertmanager.1 asomir/alertmanager:latest master-1 Running Running about a minute ago
013atvpy3wpo DEV_prometheus.1 asomir/prometheus:latest master-1 Running Running about a minute ago
836bqjmxzknt DEV_post.1 asomir/post:latest worker-1 Running Running 39 minutes ago
vd95hw5ezi1x DEV_mongo.1 mongo:3.2 master-1 Running Running 39 minutes ago
waqt3zjg2sli DEV_comment.1 asomir/comment:latest worker-2 Running Running 39 minutes ago
j7sipv4mhhrv DEV_ui.1 asomir/ui:latest worker-1 Running Running 39 minutes ago
yle6wlbgt7ls DEV_comment.2 asomir/comment:latest worker-1 Running Running 14 minutes ago
o0je63lkz2c3 DEV_ui.2 asomir/ui:latest worker-2 Running Running 14 minutes ago
06yr6yn151vo DEV_post.2 asomir/post:latest worker-2 Running Running 14 minutes ago
r5k2puu687cb DEV_ui.3 asomir/ui:latest worker-1 Running Running 7 minutes ago
docker-machine create --driver google \
--google-project docker-194414 \
--google-zone europe-west1-b \
--google-machine-type g1-small \
--google-machine-image $(gcloud compute images list --filter ubuntu-1604-lts --uri) \
worker-3
eval $(docker-machine env worker-3)
docker swarm join --token SWMTKN-1-0tg5nnnphxggtdp85nljlrhk4gjbw9kkheuh0oddldua4sva4b-37xt5a2kuc700a7pa49aui812 10.132.0.2:2377
This node joined a swarm as a worker.
docker stack ps DEV
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
tdltc39nj8zi DEV_node-exporter.sxvb300vi5n1qw3oj5hhxdoeu prom/node-exporter:v0.15.2 worker-3 Running Running about a minute ago
0db4c3irczni DEV_node-exporter.dfiw33hegvqyg9czngss6lp88 prom/node-exporter:v0.15.2 worker-2 Running Running 7 minutes ago
tsokokwzs79k DEV_node-exporter.wqrt3dr64i3md1i1tej6ek1kd prom/node-exporter:v0.15.2 worker-1 Running Running 7 minutes ago
lld3cn9wt71c DEV_node-exporter.xu9sufx2b8awxw29fkb55o5o8 prom/node-exporter:v0.15.2 master-1 Running Running 7 minutes ago
vajezlu3lwvr DEV_grafana.1 grafana/grafana:5.0.0 master-1 Running Running 7 minutes ago
qdw3f5p02sx2 DEV_cadvisor.1 google/cadvisor:v0.29.0 worker-2 Running Running 7 minutes ago
c12ifi1ogvjp DEV_alertmanager.1 asomir/alertmanager:latest master-1 Running Running 7 minutes ago
013atvpy3wpo DEV_prometheus.1 asomir/prometheus:latest master-1 Running Running 7 minutes ago
836bqjmxzknt DEV_post.1 asomir/post:latest worker-1 Running Running 45 minutes ago
vd95hw5ezi1x DEV_mongo.1 mongo:3.2 master-1 Running Running 45 minutes ago
waqt3zjg2sli DEV_comment.1 asomir/comment:latest worker-2 Running Running 45 minutes ago
j7sipv4mhhrv DEV_ui.1 asomir/ui:latest worker-1 Running Running 45 minutes ago
yle6wlbgt7ls DEV_comment.2 asomir/comment:latest worker-1 Running Running 19 minutes ago
o0je63lkz2c3 DEV_ui.2 asomir/ui:latest worker-2 Running Running 20 minutes ago
06yr6yn151vo DEV_post.2 asomir/post:latest worker-2 Running Running 20 minutes ago
r5k2puu687cb DEV_ui.3 asomir/ui:latest worker-1 Running Running 13 minutes ago
Видим, что на worker-3 появился только DEV_node-exporter.sxvb300vi5n1qw3oj5hhxdoeu
post:
image: ${USER_NAME}/post:latest
deploy:
mode: replicated
replicas: 3
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
comment:
image: ${USER_NAME}/comment:latest
deploy:
mode: replicated
replicas: 3
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
ui:
image: ${USER_NAME}/ui:latest
deploy:
mode: replicated
replicas: 3
placement:
constraints:
- node.role == worker
ports:
- "${UI_PORT}:9292/tcp"
networks:
- front_net
docker stack deploy --compose-file=<(docker-compose -f docker-compose.yml config 2>/dev/null) DEV
docker stack ps DEV
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
tdltc39nj8zi DEV_node-exporter.sxvb300vi5n1qw3oj5hhxdoeu prom/node-exporter:v0.15.2 worker-3 Running Running 7 minutes ago
0db4c3irczni DEV_node-exporter.dfiw33hegvqyg9czngss6lp88 prom/node-exporter:v0.15.2 worker-2 Running Running 14 minutes ago
tsokokwzs79k DEV_node-exporter.wqrt3dr64i3md1i1tej6ek1kd prom/node-exporter:v0.15.2 worker-1 Running Running 14 minutes ago
lld3cn9wt71c DEV_node-exporter.xu9sufx2b8awxw29fkb55o5o8 prom/node-exporter:v0.15.2 master-1 Running Running 14 minutes ago
vajezlu3lwvr DEV_grafana.1 grafana/grafana:5.0.0 master-1 Running Running 13 minutes ago
qdw3f5p02sx2 DEV_cadvisor.1 google/cadvisor:v0.29.0 worker-2 Running Running 14 minutes ago
c12ifi1ogvjp DEV_alertmanager.1 asomir/alertmanager:latest master-1 Running Running 13 minutes ago
013atvpy3wpo DEV_prometheus.1 asomir/prometheus:latest master-1 Running Running 13 minutes ago
836bqjmxzknt DEV_post.1 asomir/post:latest worker-1 Running Running about an hour ago
vd95hw5ezi1x DEV_mongo.1 mongo:3.2 master-1 Running Running about an hour ago
waqt3zjg2sli DEV_comment.1 asomir/comment:latest worker-2 Running Running about an hour ago
j7sipv4mhhrv DEV_ui.1 asomir/ui:latest worker-1 Running Running about an hour ago
yle6wlbgt7ls DEV_comment.2 asomir/comment:latest worker-1 Running Running 26 minutes ago
o0je63lkz2c3 DEV_ui.2 asomir/ui:latest worker-2 Running Running 26 minutes ago
06yr6yn151vo DEV_post.2 asomir/post:latest worker-2 Running Running 26 minutes ago
mtzgtmyr33ex DEV_post.3 asomir/post:latest worker-3 Running Preparing 28 seconds ago
uoksf6n7k4w0 DEV_comment.3 asomir/comment:latest worker-3 Running Preparing 39 seconds ago
r5k2puu687cb DEV_ui.3 asomir/ui:latest worker-1 Running Running 20 minutes ago
Задание со звездой:
Видим, что сервисы распределились равномерно по всем нодам. node-exporter автоматически запустился на новой ноде, как только мы её добавили. Делаем вывод, что её заставил так вести mode: global
У ui-компоненты приложения уже должен быть выставлен expose-порт, поэтому дополнять там ничего не нужно.
ports:
- "${UI_PORT}:9292/tcp"
Однако отметим, что внутренний механизм Routing mesh обеспечивает балансировку запросов пользователей между контейнерами UI-сервиса.
- В независимости от того, на какую ноду прийдет запрос, мы попадем на приложение (которое было опубликовано)
- Каждое новое TCP/UDP-соединение будет отправлено на следующий endpoint (round-robin балансировка)
$ docker service ps DEV_ui
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
j7sipv4mhhrv DEV_ui.1 asomir/ui:latest worker-1 Running Running 10 hours ago
o0je63lkz2c3 DEV_ui.2 asomir/ui:latest worker-2 Running Running 10 hours ago
r5k2puu687cb DEV_ui.3 asomir/ui:latest worker-1 Running Running 10 hours ago
docker-machine ip $(docker-machine ls -q)
worker-1
http://35.205.186.249:9292/
Microservices Reddit in 5e545064a8f6 container
master-1
http://35.205.239.130:9292/
Microservices Reddit in 2ef6f9aac0ee container
worker-2
http://35.189.241.95:9292/
Microservices Reddit in 5e545064a8f6 container
worker-3
http://35.205.173.12:9292/
Microservices Reddit in 84ab726a4fc9 container
4. ID не сходятся, потому что рамках кластера минимальная единица - это задача (task). Контейнер - лишь конкретный экземпляр задачи.
docker inspect $(docker stack ps DEV -q --filter "Name=DEV_ui.1") --format "{{.Status.ContainerStatus.ContainerID}}"
Чтобы обеспечить минимальное время простоя приложения во время обновлений (zero-downtime), сконфигурируем параметры деплоя параметром update_config
service:
image: svc
deploy:
update_config:
parallelism: 2 # cколько контейнеров (группу) обновить одновременно?
delay: 5s # задержка между обновлениями групп контейнеров
failure_action: rollback # что делать, если при обновлении возникла ошибка
monitor: 5s # сколько следить за обновлением, пока не признать его удачным или ошибочным
max_failure_ratio: 2 # сколько раз обновление может пройти с ошибкой перед тем, как перейти к failure_action
order: start-first # порядок обновлений (сначала убиваем старые и запускаем новые или наоборот) (только в compose 3.4)
- parallelism - cколько контейнеров (группу) обновить одновременно?
- delay - задержка между обновлениями групп контейнеров
- order - порядок обновлений (сначала убиваем старые и запускаем новые или наоборот) (только в compose 3.4) Обработка ошибочных ситуаций
- failure_action - что делать, если при обновлении возникла ошибка
- monitor - сколько следить за обновлением, пока не признать его удачным или ошибочным
- max_failure_ratio - сколько раз обновление может пройти с ошибкой перед тем, как перейти к failure_action
В случае возникновения проблем деплой останавливается (Старые и новые версии работают вместе)
services:
ui:
image: ${USER_NAME}/ui:${UI_VERSION}
deploy:
replicas: 3
update_config:
delay: 5s
parallelism: 1
failure_action: rollback
mode: replicated
placement:
constraints:
- node.role == worker
ports:
- "${UI_PORT}:9292/tcp"
networks:
- front_net
Что делать, если обновление прошло с ошибкой?
- rollback - откатить все задачи на предыдущую версию
- pause (default) - приостановить обновление
- continue - продолжить обновление
docker stack deploy --compose-file=<(docker-compose -f docker-compose.yml config 2>/dev/null) DEV
docker service ps DEV_ui
Определить update_config для сервисов post и comment так, чтобы они обновлялись группами по 2 сервиса с разрывом в 10 секунд, а в случае неудач осуществлялся rollback.
Отразить изменения в docker-compose.yml
post:
image: ${USER_NAME}/post:latest
deploy:
mode: replicated
replicas: 3
update_config:
delay: 10s
parallelism: 2
failure_action: rollback
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
comment:
image: ${USER_NAME}/comment:latest
deploy:
mode: replicated
replicas: 3
update_config:
delay: 10s
parallelism: 2
failure_action: rollback
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
ui:
image: ${USER_NAME}/ui:${UI_VERSION}
deploy:
replicas: 3
update_config:
delay: 5s
parallelism: 1
failure_action: rollback
mode: replicated
placement:
constraints:
- node.role == worker
ports:
- "${UI_PORT}:9292/tcp"
networks:
- front_net
С помощью resources limits описываем максимум потребляемых приложениями CPU и памяти. Это обеспечит нам:
- Представление о том, сколько ресурсов нужно приложению
- Контроль Docker за тем, чтобы никто не превысил заданного порога (с помощью cgroups)
- Защиту сторонних приложений от неконтролируемого расхода ресурса контейнером
Задать ограничения ресурсов для сервисов post и comment, ограничив каждое в 300 мегабайт памяти и в 30% процессорного времени. Изменения отразить в docker-compose.yml
post:
image: ${USER_NAME}/post:latest
deploy:
resources:
limits:
cpus: '0.30'
memory: 300M
mode: replicated
replicas: 3
update_config:
delay: 10s
parallelism: 2
failure_action: rollback
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
comment:
image: ${USER_NAME}/comment:latest
deploy:
resources:
limits:
cpus: '0.30'
memory: 300M
mode: replicated
replicas: 3
update_config:
delay: 10s
parallelism: 2
failure_action: rollback
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
ui:
image: ${USER_NAME}/ui:${UI_VERSION}
deploy:
resources:
limits:
cpus: '0.50'
memory: 150M
replicas: 3
update_config:
delay: 5s
parallelism: 1
failure_action: rollback
mode: replicated
placement:
constraints:
- node.role == worker
ports:
- "${UI_PORT}:9292/tcp"
networks:
- front_net
Если контейнер в рамках задачи завершит свою работу, то планировщик Swarm автоматически запустит новый (даже если он вручную остановлен).
Мы можем поменять это поведение (для целей диагностики, например) так, чтобы контейнер перезапускался только при падении контейнера (on-failure).
По-умолчанию контейнер будет бесконечно перезапускаться. Это может оказать сильную нагрузку на машину в целом.
ui:
image: ${USER_NAME}/ui:${UI_VERSION}
deploy:
restart_policy:
condition: on-failure
max_attempts: 3
delay: 3s
resources:
limits:
cpus: '0.50'
memory: 150M
replicas: 3
update_config:
delay: 5s
parallelism: 1
failure_action: rollback
mode: replicated
placement:
constraints:
- node.role == worker
Задайте политику перезапуска для comment и post сервисов так, чтобы Swarm пытался перезапустить их при падении с ошибкой 10-15 раз с интервалом в 1 секунду.
post:
image: ${USER_NAME}/post:latest
deploy:
restart_policy:
condition: on-failure
max_attempts: 12
delay: 1s
resources:
limits:
cpus: '0.30'
memory: 300M
mode: replicated
replicas: 3
update_config:
delay: 10s
parallelism: 2
failure_action: rollback
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
comment:
image: ${USER_NAME}/comment:latest
deploy:
restart_policy:
condition: on-failure
max_attempts: 13
delay: 1s
resources:
limits:
cpus: '0.30'
memory: 300M
mode: replicated
replicas: 3
update_config:
delay: 10s
parallelism: 2
failure_action: rollback
placement:
constraints:
- node.role == worker
networks:
- front_net
- back_net
docker stack deploy --compose-file=<(docker-compose -f docker-compose.yml config 2>/dev/null) DEV
Помимо сервисов приложения, у вас может быть инфраструктура, описанная в compose-файле (prometheus, node- exporter, grafana ...)
Нужно выделить ее в отдельный compose-файл. С названием docker-compose-monitoring.yml В него выносится все что относится к этим сервисам (volumes, services) Запускать приложение вместе с мониторингом можно следующей командой (команда не работает, выпадает ошибка "Top-level object must be a mapping", хотя частям запускается)
docker stack deploy --compose-file=<(docker-compose -f docker-compose-monitoring.yml -f docker-compose.yml config 2>/dev/null) DEV
• Сбор неструктурированных логов • Визуализация логов • Сбор структурированных логов • Распределенная трасировка
export GOOGLE_PROJECT=docker-194414
docker-machine create --driver google \
--google-machine-image https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts \
--google-machine-type n1-standard-1 \
--google-open-port 5601/tcp \
--google-open-port 9292/tcp \
--google-open-port 9411/tcp \
logging
eval $(docker-machine env logging)
docker-machine ip logging
35.193.44.87
• Экспортируем имя пользователя
export USER_NAME=asomir
• Обновили код в директории /src вашего репозитория из кода по ссылке.
ЗАБЫЛИ ДОКЕРФАЙЛЫ, ВЕРНУЛИ НА МЕСТО!
• Выполнили сборку образов при помощи скриптов docker_build.sh в директории каждого сервиса:
for i in ui post-py comment; do cd src/$i; bash docker_build.sh; cd -; done
Как упоминалось на лекции хранить все логи стоит централизованно: на одном (нескольких) серверах. В этом ДЗ мы построим рассмотрим пример построения системы централизованного логирования на примере Elastic стека (ранее известного как ELK): который включает в себя 3 осовных компонента:
• ElasticSearch (TSDB и поисковый движок для хранения данных),
• Logstash (для агрегации и трансформации данных),
• Kibana (для визуализации).
Однако для агрегации логов вместо Logstash мы будем использовать Fluentd, таким образом получая еще одно популярное сочетание этих инструментов, получившее название EFK.
docker/docker-compose-logging.yml
version: '3'
services:
fluentd:
build: ./fluentd
ports:
- "24224:24224"
- "24224:24224/udp"
networks:
- back_net
- front_net
elasticsearch:
image: elasticsearch
expose:
- 9200
ports:
- "9200:9200"
networks:
- back_net
- front_net
kibana:
image: kibana
ports:
- "5601:5601"
networks:
- back_net
- front_net
networks:
back_net:
front_net:
gcloud compute firewall-rules create fluentd-default --allow tcp,udp:24224
gcloud compute firewall-rules create elasticsearch-default --allow tcp:9200
gcloud compute firewall-rules create kibana-default --allow tcp:5601
gcloud compute firewall-rules create zipkin-default --allow tcp:9411
Fluentd инструмент, который может использоваться для отправки, агрегации и преобразования лог-сообщений. Мы будем использовать Fluentd для агрегации (сбора в одной месте) и парсинга логов сервисов нашего приложения. Создадим образ Fluentd с нужной нам конфигурацией.
Создали в вашем проекте microservices директорию docker/fluentd, в ней, создали Dockerfile со следущим содержимым:
FROM fluent/fluentd:v0.12
RUN gem install fluent-plugin-elasticsearch --no-rdoc --no-ri --version 1.9.5
RUN gem install fluent-plugin-grok-parser --no-rdoc --no-ri --version 1.0.0
ADD fluent.conf /fluentd/etc
<source>
@type forward # плагин in_forward для приёма логов
port 24224
bind 0.0.0.0
</source>
<match *.**>
@type copy # copy плагин переправляет все логи в elasticsearch и выводит в output
<store>
@type elasticsearch
host elasticsearch
port 9200
logstash_format true
logstash_prefix fluentd
logstash_dateformat %Y%m%d
include_tag_key true
type_name access_log
tag_key @log_name
flush_interval 1s
</store>
<store>
@type stdout
</store>
</match>
Логи должны иметь заданную (единую) структуру и содержать необходимую для нормальной эксплуатации данного сервиса информацию о его работе.
Лог-сообщения также должны иметь понятный для выбранной системы логирования формат, чтобы избежать ненужной траты ресурсов на преобразование данных в нужный вид. Структурированные логи мы рассмотрим на примере сервиса post.
docker/
docker-compose up -d
docker/
docker-compose logs -f post
Attaching to reddit_post_1
docker-compose -f docker-compose-logging.yml up -d
docker-compose down
docker-compose up -d
Kibana - инструмент для визуализации и анализа логов от компании Elastic. Откроем WEB-интерфейс Kibana для просмотра собранных в ElasticSearch логов Post-сервиса (kibana слушает на порту 5601)
Введем в поле патерна индекса fluentd-* И создадим новый индекс (Create)
Нажмем “Discover”, чтобы посмотреть информацию ополученных лог сообщениях
График покажет в какой момент времени поступало то или иное количество лог сообщений
Нажмем на знак “развернуть” напротив одного из лог сообщений, чтобы посмотреть подробную информацию о нем
Видим лог-сообщение, которые мы недавно наблюдали в терминале. Теперь эти лог-сообщения хранятся централизованно в ElasticSearch. Также видим доп. информацию о том, откуда поступил данный лог.
Обратим внимание на то, что наименования в левом столбце, называются полями. По полям можно производить поиск для быстрого нахождения нужной информации
К примеру, посмотрев список доступных полей, мы можем выполнить поиск всех логов, поступивших с контейнера reddit_post_1
Заметим, что поле log содержит в себе JSON объект, который содержит много интересной нам информации.
Нам хотелось бы выделить эту информацию в поля, чтобы иметь возможность производить по ним поиск. Например, для того чтобы найти все логи, связанные с определенным событием (event) или конкретным сервисов (service). Мы можем достичь этого за счет использования фильтров для выделения нужной информации.
Добавим фильтр для парсинга json логов, приходящих от post сервиса, в конфиг fluentd
<source>
@type forward
port 24224
bind 0.0.0.0
</source>
<filter service.post>
@type parser
format json
key_name log
</filter>
<match *.**>
@type copy
<store>
@type elasticsearch
host elasticsearch
port 9200
logstash_format true
logstash_prefix fluentd
logstash_dateformat %Y%m%d
include_tag_key true
type_name access_log
tag_key @log_name
flush_interval 1s
</store>
<store>
@type stdout
</store>
</match>
docker-compose -f docker-compose-logging.yml up -d --build
Вновь обратимся к Kibana. Прежде чем смотреть логи убедимся, что временной интервал выбран верно. Нажмите один раз на дату со временем
Неструктурированные логи отличаются отсутствием четкой структуры данных. Также часто бывает, что формат лог- сообщений не подстроен под систему централизованного логирования, что существенно увеличивает затраты вычислительных и временных ресурсов на обработку данных и выделение нужной информации. На примере сервиса ui мы рассмотрим пример логов с неудобным форматом сообщений.
По аналогии с post сервисом определим для ui сервиса драйвер для логирования fluentd в compose-файле.
version: '3.3'
services:
post_db:
image: mongo:${VERSION_MONGO}
volumes:
- post_db:/data/db
networks:
back_net:
aliases:
- post_db
- comment_db
ui:
container_name: ui
image: ${USERNAME}/ui:latest
ports:
- ${APP_PORT}:9292/tcp
networks:
- front_net
logging:
driver: "fluentd"
options:
fluentd-address: localhost:24224
tag: service.post
post:
image: ${USER_NAME}/post:latest
environment:
- POST_DATABASE_HOST=post_db
- POST_DATABASE=posts
depends_on:
- post_db
ports:
- "5000:5000"
networks:
- back_net
- front_net
logging:
driver: "fluentd"
options:
fluentd-address: localhost:24224
tag: service.post
comment:
container_name: comment
image: ${USERNAME}/comment:latest
networks:
- back_net
- front_net
volumes:
post_db:
prometheus_data:
networks:
front_net:
back_net:
docker-compose stop ui
docker-compose rm ui
docker-compose up -d
Когда приложение или сервис не пишет структурированные логи, приходится использовать старые добрые регулярные выражения для их парсинга.
Следующее регулярное выражение нужно, чтобы успешно выделить интересующую нас информацию из лога UI-сервиса в поля
source>
@type forward
port 24224
bind 0.0.0.0
</source>
<filter service.post>
@type parser
format json
key_name log
</filter>
<filter service.ui>
@type parser
format /\[(?<time>[^\]]*)\] (?<level>\S+) (?<user>\S+)[\W]*service=(?<service>\S+)[\W]*event=(?<event>\S+)[\W]*(?:path=(?<path>\S+)[\W]*)?request_id=(?<request_id>\S+)[\W]*(?:remote_addr=(?<remote_addr>\S+)[\W]*)?(?:method= (?<method>\S+)[\W]*)?(?:response_status=(?<response_status>\S+)[\W]*)?(?:message='(?<message>[^\']*)[\W]*)?/
key_name log
</filter>
<match *.**>
@type copy
<store>
@type elasticsearch
host elasticsearch
port 9200
logstash_format true
logstash_prefix fluentd
logstash_dateformat %Y%m%d
include_tag_key true
type_name access_log
tag_key @log_name
flush_interval 1s
</store>
<store>
@type stdout
</store>
</match>
Созданные регулярки могут иметь ошибки, их сложно менять и невозможно читать. Для облегчения задачи парсинга вместо стандартных регулярок можно использовать grok-шаблоны. По-сути grok’и - это именованные шаблоны регулярных выражений (очень похоже на функции). Можно использовать готовый regexp, просто сославшись на него как на функцию.
<source>
@type forward
port 24224
bind 0.0.0.0
</source>
<filter service.post>
@type parser
format json
key_name log
</filter>
<filter service.ui>
@type parser
key_name log
format grok
grok_pattern %{RUBY_LOGGER}
</filter>
<match *.**>
@type copy
<store>
@type elasticsearch
host elasticsearch
port 9200
logstash_format true
logstash_prefix fluentd
logstash_dateformat %Y%m%d
include_tag_key true
type_name access_log
tag_key @log_name
flush_interval 1s
</store>
<store>
@type stdout
</store>
</match>
gcloud compute firewall-rules create zipkin-default --allow tcp:9411
version: '3'
services:
zipkin:
image: openzipkin/zipkin
ports:
- "9411:9411"
fluentd:
build: ./fluentd
ports:
- "24224:24224"
- "24224:24224/udp"
elasticsearch:
image: elasticsearch
expose:
- 9200
ports:
- "9200:9200"
kibana:
image: kibana
ports:
- "8080:5601"
docker-compose -f docker-compose-logging.yml -f docker-compose.yml down
docker-compose -f docker-compose-logging.yml -f docker-compose.yml up -d
export GOOGLE_PROJECT=docker-194414
docker-machine create --driver google \
--google-machine-image https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts \
--google-machine-type n1-standard-1 \
vm1
eval $(docker-machine env vm1)
docker-machine ip vm1
35.192.64.106
cd docker/
docker-compose up -d
Оставим описание приложений в docker-compose.yml, а мониторинг выделим в отдельный файл docker-compose-monitoring.yml
Для запуска приложений будем как и ранее использовать
docker-compose up -d
Для мониторинга -
docker-compose -f docker-compose-monitoring.yml up -d
Мы будем использовать cAdvisor для наблюдения за состоянием наших Docker контейнеров. cAdvisor собирает информацию о ресурсах потребляемых контейнерами и характеристиках их работы. Примерами метрик являются: процент использования контейнером CPU и памяти, выделенные для его запуска, объем сетевого трафика и др
Поместим его в одну сеть с Прометеем, чтобы он мог собирать с него метрички
docker-compose-monitoring.yml
cadvisor:
image: google/cadvisor:v0.29.0
volumes:
- '/:/rootfs:ro'
- '/var/run:/var/run:rw'
- '/sys:/sys:ro'
- '/var/lib/docker/:/var/lib/docker:ro'
ports:
- '8080:8080'
Откроем порт 8080 в GCE
gcloud compute firewall-rules create cadvisor-default --allow tcp:8080
- job_name: 'cadvisor'
static_configs:
- targets:
- 'cadvisor:8080'
export USER_NAME=asomir
docker build -t $USER_NAME/prometheus .
cd ../ ../docker
docker-compose down
docker-compose up -d
docker-compose -f docker-compose-monitoring.yml up -d
Откроем страницу Web UI по адресу http://104.154.181.55:8080
по пути /metrics все собираемые метрики публикуются для сбора Prometheus
Subcontainers
comment (/docker/f03c8787bcd9bd98d74f24bc99c81ca959e6b2060e800efcc8454576dc4ffe0b)
post-py (/docker/99c47e0f760bfb1e115ec71ac01a835b1f02f213698f62231d78736ed49ddb66)
ui (/docker/80e8d9da6954faf0eb04a8b665f9a1ae53c6b7834f8cc4b4df307585cf58cd0a)
docker_post_db_1 (/docker/34e1634d3c0251e55c2bd608b58af6ea40151e944d31a80bd0173df45fef3ca6)
docker_node-exporter_1 (/docker/48f5424a7383963b288e9a830f1ca10997ca1bdde526cafc9b67a884935f4d5b)
docker_prometheus_1 (/docker/24c99069cfb8b0ec891bae5a31d947780fa1f1357f91bcf176a60a48d4e8bb66)
docker_cadvisor_1 (/docker/594e93a52e967d2a00a373d3cd776aad9ea504caf912cc4ca2adc808f06c0717)
Driver Status
Docker Version 18.02.0-ce
Docker API Version 1.36
Kernel Version 4.13.0-1011-gcp
OS Version Ubuntu 16.04.4 LTS
Host Name vm1
Docker Root Directory /var/lib/docker
Execution Driver
Number of Images 9
Number of Containers 7
Storage
Driver aufs
Root Dir /var/lib/docker/aufs
Backing Filesystem extfs
Dirs 84
Dirperm1 Supported true
Проверим, что метрики контейнеров собираются
Prometheus. Введем, слово container
и посмотрим,
что он предложит дополнить
Добавим новый сервис в docker-compose-monitoring.yml
grafana:
image: grafana/grafana:5.0.0
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=secret
depends_on:
- prometheus
ports:
- 3000:3000
volumes:
grafana_data:
docker-compose -f docker-compose-monitoring.yml up -d grafana
gcloud compute firewall-rules create grafana-default --allow tcp:3000
И введём admin secret
Сервер назовём Prometheus Server и добавим его по адресу http://104.154.181.55:9090
Импортируем Dashboard с сайта https://grafana.com/dashboards : Data Source "Prometheus" - Category "Docker"
Загрузили источник данных для визуализации Prometheus Server
Добавим информацию о post сервисе в конфигурацию Prometheus, чтобы он начал собирать метрики и с него.
- job_name: 'post'
static_configs:
- targets:
- 'post:5000'
$ export USER_NAME=username
$ docker build -t $USER_NAME/prometheus .
$ docker-compose -f docker-compose-monitoring.yml down
$ docker-compose -f docker-compose-monitoring.yml up -d
И добавим несколько постов в приложении и несколько коментов, чтобы собрать значения метрик приложения.
rate(ui_request_count{http_status=~"^[45].*"}[1m])
rate(ui_request_count{http_status=~"^[23].*"}[1m])
В Prometheus есть тип метрик histogram. Данный тип метрик в качестве своего значение отдает ряд распределения измеряемой величины в заданном интервале значений. Мы используем данный тип метрики для измерения времени обработки HTTP запроса нашим приложением.
Посмотрим информацию по времени обработки запроса приходящих на главную страницу приложения
ui_request_latency_seconds_bucket{path="/"}
-
числовое значение в наборе значений
-
все числа в наборе меньше перцентиля, попадают в границы заданного процента значений от всего числа значений в наборе
Часто для анализа данных мониторинга применяются значения 90, 95 или 99-й перцентиля. Мы вычислим 95-й перцентиль для выборки времени обработки запросов, чтобы посмотреть какое значение является максимальной границей для большинства (95%) запросов. Для этого воспользуемся встроенной функцией histogram_quantile():
histogram_quantile(0.95, sum(rate(ui_request_latency_seconds_bucket[5m])) by (le))
Сохраним изменения дашборда и эспортируем его в JSON файл, который загрузим на нашу локальную машину
monitoring/grafana/dashboards/UI_Service_Monitoring.json
В качестве примера метрик бизнес логики мы в наше приложение мы добавили счетчики количества постов и комментариев.
• post_count
• comment_count
Мы построим график скорости роста значения счетчика за последний час, используя функцию rate(). Это позволит нам получать информацию об активности пользователей приложения.
построили график функции rate(comment_count[1h]) и экспортировали график в monitoring/grafana/dashboards/Business_Logic_Monitoring.json
Мы определим несколько правил, в которых зададим условия состояний наблюдаемых систем, при которых мы должны получать оповещения, т.к. заданные условия могут привести к недоступности или неправильной работе нашего приложения
Alertmanager - дополнительный компонент для системы мониторинга Prometheus, который отвечает за первичную обработку алертов и дальнейшую
отправку оповещений по заданному назначению. Создаём новую директорию monitoring/alertmanager. В этой директории создаём Dockerfile со следующим содержимым:
FROM prom/alertmanager:v0.14.0
ADD config.yml /etc/alertmanager/
В директории monitoring/alertmanager создали файл config.yml, в котором определили отправку нотификаций в мой тестовый слак канал #alexander-akilin.
global:
slack_api_url: 'https://hooks.slack.com/services/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'
route:
receiver: 'slack-notifications'
receivers:
- name: 'slack-notifications'
slack_configs:
- channel: '#alexander-akilin'
monitoring/alertmanager
docker build -t $USER_NAME/alertmanager .
alertmanager:
image: ${USER_NAME}/alertmanager
command:
- '--config.file=/etc/alertmanager/config.yml'
ports:
- 9093:9093
monitoring/prometheus/Dockerfile
FROM prom/prometheus:v2.1.0
ADD prometheus.yml /etc/prometheus/
ADD alerts.yml /etc/prometheus/
prometheus.yml
rule_files:
- "alerts.yml"
alerting:
alertmanagers:
- scheme: http
static_configs:
- targets:
- "alertmanager:9093"
docker build -t $USER_NAME/prometheus .
docker-compose -f docker-compose-monitoring.yml down
docker-compose -f docker-compose-monitoring.yml up -d
gcloud compute firewall-rules create alertmanager-default --allow tcp:9093
$ docker login Login Succeeded
docker push $USER_NAME/ui
docker push $USER_NAME/comment
docker push $USER_NAME/post
docker push $USER_NAME/prometheus
docker push $USER_NAME/alertmanager
https://hub.docker.com/r/asomir/
$ gcloud compute firewall-rules create prometheus-default --allow tcp:9090
$ gcloud compute firewall-rules create puma-default --allow tcp:9292
export GOOGLE_PROJECT=docker-194414
# create docker host
docker-machine create --driver google \
--google-machine-image https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts \
--google-machine-type n1-standard-1 \
vm1
# configure local env
eval $(docker-machine env vm1)
$ docker run --rm -p 9090:9090 -d --name prometheus prom/prometheus:v2.1.0
проверяем ШуЗы Прометея
docker-machine ip vm1
и идём по полученному ШуЗу http://35.225.212.35:9090/graph
Targets (цели) - представляют собой системы или процессы, за которыми следит Prometheus. Помним, что Prometheus является pull системой, поэтому он постоянно делает HTTP запросы на имеющиеся у него адреса (endpoints). Посмотрим текущий список целей
В Targets сейчас мы видим только сам Prometheus. У каждой цели есть свой список адресов (endpoints), по которым следует обращаться для получения информации.
В веб интерфейсе мы можем видеть состояние каждого endpoint-а (up); лейбл (instance="someURL"), который Prometheus автоматически добавляет к каждой метрике, получаемой с данного endpoint-а; а также время, прошедшее с момента последней операции сбора информации с endpoint-а.
Также здесь отображаются ошибки при их наличии и можно отфильтровать только неживые таргеты.
Мы можем открыть страницу в веб браузере по данному HTTP пути (host:port/metrics), чтобы посмотреть, как выглядит та информация, которую собирает Prometheus.
docker stop prometheus
6. Создаём докер файл monitoring/prometheus/Dockerfile, который копирует файл конфигурации с нашей машины внутрь контейнера:
FROM prom/prometheus:v2.1.0
ADD prometheus.yml /etc/prometheus/
global:
scrape_interval: '5s' # частота сбора метрик
scrape_configs: # Эндпойнты - группы метрик, собирающих одинаковые данные
- job_name: 'prometheus'
static_configs:
- targets:
- 'localhost:9090' # адрес, откуда чо собираем
- job_name: 'ui'
static_configs:
- targets:
- 'ui:9292'
- job_name: 'comment'
static_configs:
- targets:
- 'comment:9292'
$ export USER_NAME=asomir
$ docker build -t $USER_NAME/prometheus .
9. Сборку образов производим при помощи скриптов docker_build.sh, которые есть в директории каждого сервиса. С его помощью мы добавим информацию из Git в наш healthcheck.
Запустиим сразу все из корня репы и пойдём пить кофе
for i in ui post-py comment; do cd src/$i; bash
docker_build.sh; cd -; done
version: '3.3'
services:
post_db:
image: mongo:${VERSION_MONGO}
volumes:
- post_db:/data/db
networks:
back_net:
aliases:
- post_db
- comment_db
ui:
container_name: ui
image: ${USERNAME}/ui:latest
ports:
- ${APP_PORT}:9292/tcp
networks:
- front_net
post:
container_name: post
image: ${USERNAME}/post:latest
networks:
- front_net
- back_net
comment:
container_name: comment
image: ${USERNAME}/comment:latest
networks:
- back_net
- front_net
prometheus:
image: ${USER_NAME}/prometheus
ports:
- '9090:9090'
volumes:
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention=1d'
networks:
- back_net
- front_net
volumes:
post_db:
prometheus_data:
networks:
front_net:
back_net:
Запускаем docker-compose up -d и проверяем работоспособность
http://35.225.212.35:9090/graph http://35.225.212.35:9292/
Проверяем состояние наших эндпойнтов: comment, ui, prometheus
http://35.225.212.35:9090/targets
Healthcheck-и представляют собой проверки того, что наш сервис здоров и работает в ожидаемом режиме. В нашем случае healthcheck выполняется внутри кода микросервиса и выполняет проверку того, что все сервисы, от которых зависит его работа, ему доступны. Если требуемые для его работы сервисы здоровы, то healthcheck проверка возвращает status = 1, что соответсвует тому, что сам сервис здоров. Если один из нужных ему сервисов нездоров или недоступен, то проверка вернет status = 0.
Выполнили поиск в веб-интерфейсе прометея ui_health, однако он ничего не нашёл. Зашёл на сайт реддит и сделал пост, - после этого Прометей нашёл метрику
ui_health{branch="monitoring-1",commit_hash="d3d08a6",instance="ui:9292",job="ui",version="0.0.1"}
Где показано название ветки в гите, коммите и лейблах
Перешли в графическое отображение, увидели единицу, и она прекрасна. Остановили сервис post.
docker-compose stop post
график упал в ноль. И это ужасно. Поплачем друзья, ведь сервис нездоров.
Зашёл посмотреть на ui_health_comment_availability и вижу прекрасную единицу.
Зашёл глянуть на ui_health_post_availability и обожечки! Что я вижу! Полный ноль! Сервис мёртв! Что же делать?!
Поднимем же сервис пост, вставай, дружочек:
docker-compose start post
ui_health_post_availability{branch="monitoring-1",commit_hash="d3d08a6",instance="ui:9292",job="ui",version="0.0.1"}
Жив и здоров, мы пришили ему ножки!
• Программа, которая делает метрики доступными для сбора Prometheus
• Дает возможность конвертировать метрики в нужный для Prometheus формат
• Используется когда нельзя поменять код приложения
• Примеры: PostgreSQL, RabbitMQ, Nginx, Node exporter, cAdvisor
services:
node-exporter:
image: prom/node-exporter:v0.15.2
user: root
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.ignored-mount-points="^/(sys|proc|dev|host|etc)($$|/)"'
- job_name: 'node'
static_configs:
- targets:
- 'node-exporter:9100'
monitoring/prometheus
$ docker build -t $USER_NAME/prometheus .
$ docker-compose down
$ docker-compose up -d
В списке эндпойнтов появился ещё один сервис node
Зайдем на хост:
docker-machine ssh vm1
Добавим нагрузки:
yes > /dev/null
Посмотрим на весёлые графики
$ docker login Login Succeeded
docker push $USER_NAME/ui docker push $USER_NAME/comment docker push $USER_NAME/post docker push $USER_NAME/prometheus
Удалили виртуалку: $ docker-machine rm vm1
https://hub.docker.com/r/asomir/
git checkout -b docker-7 git remote add gitlab2 http://35.195.25.42/homework/example2.git git push gitlab2 docker-7
image: ruby:2.4.2
stages: # в каких окружениях что происходить будет
- build
- test
- review
- stage
- production
variables:
DATABASE_URL: 'mongodb://mongo/user_posts'
before_script: # что бы запустить в самом начале
- cd reddit
- bundle install
build_job: # билдим
stage: build
script:
- echo 'Building'
test_unit_job: # юнит тестирование
stage: test
services:
- mongo:latest
script:
- ruby simpletest.rb
test_integration_job: # интегрированные тесты
stage: test
script:
- echo 'Testing 2'
deploy_dev_job: # деплой в дев
stage: review
script:
- echo 'Deploy'
environment: # разворачиваем окружение
name: dev
url: http://dev.example.com
branch review: # ветка ревью
stage: review
script: echo "Deploy to $CI_ENVIRONMENT_SLUG"
environment:
name: branch/$CI_COMMIT_REF_NAME
url: http://$CI_ENVIRONMENT_SLUG.example.com
only:
- branches
except:
- master
staging:
stage: stage
when: manual # говорит о том, что job должен быть запущен человеком из UI
only:
- /^\d+\.\d+.\d+/ # директива, которая не позволит нам выкатить на staging и production код,
# не помеченный с помощью тэга в git
# описывает список условий, которые должны быть
# истинны, чтобы job мог запуститься. Регулярное выражение слева
# означает, что должен стоять semver тэг в git, например, 2.4.10
script:
- echo 'Deploy'
environment:
name: stage
url: https://beta.example.com
production:
stage: production
when: manual
only:
- /^\d+\.\d+.\d+/
script:
- echo 'Deploy'
environment:
name: production
url: http://example.com
git commit -a -m ‘#4 add logout button to profile page’
git tag 2.4.10
git push gitlab2 docker-7 --tags
branch review:
stage: review
script: echo "Deploy to $CI_ENVIRONMENT_SLUG"
environment:
name: branch/$CI_COMMIT_REF_NAME
url: http://$CI_ENVIRONMENT_SLUG.example.com
only:
- branches
except:
- master
Теперь, на каждую ветку в git отличную от master Gitlab CI будет определять новое окружение.
#####1. Нам потребуется создать в Google Cloud новую виртуальную машину со следующими параметрами
- 1 CPU
- 3.75GB RAM
- 50-100 GB HDD
- Ubuntu 16.04
# Подключаемся к гуглу к нашему проекту в регионе eur
provider "google" {
version = "1.4.0"
project = "${var.project}"
region = "${var.region}"
}
# Подключение SSH ключей для пользователя asomirl
resource "google_compute_project_metadata" "ssh-asomirl" {
metadata {
ssh-keys = "${var.gitlab_admin}:${file(var.public_key_path)}"
}
}
# Создаём в west-1b машину n1-standard-1 Standard machine type with 1 virtual CPU and 3.75 GB of memory.
resource "google_compute_instance" "gitlab" {
name = "gitlab"
machine_type = "n1-standard-1"
zone = "${var.zone}"
# Прибавили теги, чтобы ходить по SSH
tags = ["docker-host", "default-allow-ssh"]
# определение загрузочного диска 50Gb
boot_disk {
initialize_params {
# По умолчанию "ubuntu-1604-lts"
image = "${var.disk_image}"
size = 50
}
}
# определение сетевого интерфейса
network_interface {
# сеть, к которой присоединить данный интерфейс
network = "default"
# использовать ephemeral IP для доступа из Интернет
access_config {}
}
# включаем подключение по ssh с путём к приватному ключу
connection {
type = "ssh"
user = "${var.gitlab_admin}"
agent = false
private_key = "${file(var.private_key_path)}"
}
}
# Создание правила для firewall открываем для начала все порты и протокол ICMP
resource "google_compute_firewall" "docker-host-allow" {
name = "docker-host-allow"
# Название сети, в которой действует правило
network = "default"
# Какой доступ разрешить
allow {
protocol = "tcp"
ports = ["0-65535"]
}
allow {
protocol = "icmp"
}
# Каким адресам разрешаем доступ
source_ranges = ["0.0.0.0/0"]
# Правило применимо для инстансов с тегом …
target_tags = ["docker-host"]
}
# Создаём внешний адрес для машины
resource "google_compute_address" "gitlab_ip" {
name = "gitlab-ip"
}
# Открываем порт 22
resource "google_compute_firewall" "firewall_ssh" {
name = "gitlab-allow-ssh"
network = "default"
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = "${var.source_ranges}"
}
В папке terraform-gitlab выполняем
$ terraform import google_compute_firewall.firewall_ssh defaultallow-ssh
В файле inventory указываем имя хоста gitlab и его ip,
gitlab ansible_host=35.195.130.13
Также ip внесём в /roles/gitlab/defaults/main.yml, там же укажем имя нашего пользователя и папку для установки GitLab
deploy_user: asomirl
gitlab_ip: 35.195.130.13
gitlab_folder: /srv/gitlab
3. Нам необходимо установить докер и докер композ. Из-за присущей лени мы тупо скачиваем ansible роль, которая ставит обадва сразу:
В папке с ролями выполняем скачивание роли ansible-galaxy nickjj.docker
ansible-galaxy install nickjj.docker -p ./ --force
ansible-galaxy init gitlab
# tasks file for gitlab/
- name: Install python for Ansible
raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal && apt install -y python-apt )
become: true
changed_when: false
6. После тестирования и попытки запускать наш докер контейнер выпадает ошибка отсутствия docker-py, поэтому было принято ставить докер и пипы вручную.
В файле install_pip.yml устанавливаем необходимые нам пипули с хтопом, гитом на всякий, питошами. Также указываем нашего пользователя asomirl как деплой юзверя
# Examples from Ansible Playbooks
- name: Install base packages
apt: name={{ item }} state=installed
with_items:
- htop #чтобы было удобно следить за установкой гитлаба
- git # на всякий пожарный, чтобы выкачивать всякие нужные штуки из гита
- python-pip # без этого ничего не работает
- python3-pip # так и не понял, нужна ли 3 версия, паходу разберёмся
tags:
- packages
- name: Upgrade pip # обновляем пипы на самые последние версии
pip: name=pip state=latest
tags:
- packages
- name: Create user
user:
name: "{{ deploy_user }}" # создадим нашего пользователя на серваке на всякие пожарные
comment: "Used to deploy Gitlab"
state: present
create_folders.yml
- name: Creates directoryes
file:
path: "{{ gitlab_folder }}/config"
path: "{{ gitlab_folder }}/data"
path: "{{ gitlab_folder }}/logs"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
install_docker.yml
- name: ensure repository key is installed
apt_key:
id: "58118E89F3A912897C070ADBF76221572C52609D"
keyserver: "hkp://p80.pool.sks-keyservers.net:80"
state: present
- name: ensure docker registry is available
# For Ubuntu 16.04 LTS, use this repo instead:
apt_repository: repo='deb https://apt.dockerproject.org/repo ubuntu-xenial main' state=present
- name: ensure docker and dependencies are installed
apt: name=docker-engine update_cache=yes
- service: name=docker state=restarted
install_docker_compose.yml
- name: Install docker python module
pip:
name: "docker-compose"
web:
image: 'gitlab/gitlab-ce:latest'
restart: always
hostname: 'gitlab.example.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://{{ gitlab_ip }}' # эту штуку берём из папки дефолтс
ports:
- '80:80'
- '443:443'
- '2222:22'
volumes:
- '/srv/gitlab/config:/etc/gitlab'
- '/srv/gitlab/logs:/var/log/gitlab'
- '/srv/gitlab/data:/var/opt/gitlab'
11. ...и копируем его содержимое на сервер гитлаб в приготовленную папку /srv/gitlab/ в файл docker-compose.yml
omnibus.yml
- name: Change mongo config file
template:
src: templates/docker-compose.yml.j2
dest: /srv/gitlab/docker-compose.yml
mode: 0644
compose.yml
- name: docker-compose via ansible docker_service
tags: "docker"
docker_service:
files: docker-compose.yml
project_src: /srv/gitlab/
project_name: "gitlab-service"
- include: install_py.yml
- include: install_pip.yml
- include: create_folders.yml
- include: install_docker.yml
- include: install_docker_compose.yml
- include: omnibus.yml
- include: compose.yml
terraform apply
15. Запускаем конфигурирование и установку необоходимых пакетов и компонентов для GitLab с помощью Ansible:
cd ~/asomir_microservices/ansible-gitlab/roles
ansible-playbook gitlab.yml --vv
16. И бешено радуемся установке. Пока ставится, мы заходим по ssh на созданную Машину GitLab запускаем htop и радуемся циферкам.
ssh asomirl@35.195.130.13
htop
17. Переходим в браузере по айпи 35.195.130.13 и через некоторое время видим морду лисы. Вводим дважды придуманный пароль, затем вводим имя root и этот пароль, попадаем на приветственную Страницу гитлаба
git checkout -b docker-6
git remote add gitlab http://35.195.130.13/homework/example.git
git push gitlab docker-6
stages:
- build
- test
- deploy
build_job:
stage: build
script:
- echo 'Building'
test_unit_job:
stage: test
script:
- echo 'Testing 1'
test_integration_job:
stage: test
script:
- echo 'Testing 2'
deploy_job:
stage: deploy
script:
- echo 'Deploy'
И запушили его в гитлаб в наш проект
git add .gitlab-ci.yml
git commit -m 'add pipeline definition'
git push gitlab docker-6
Specify the following URL during the Runner setup: http://35.195.130.13/
Use the following registration token during setup: qhRTVTPL5kmP4N9Tx5_U
sudo docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
docker exec -it gitlab-runner gitlab-runner register
и отвечаем на вопросы:
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://35.195.130.13/
Please enter the gitlab-ci token for this runner:
qhRTVTPL5kmP4N9Tx5_U
Please enter the gitlab-ci description for this runner:
[38689f5588fe]: my-runner
Please enter the gitlab-ci tags for this runner (comma separated):
linux,xenial,ubuntu,docker
Whether to run untagged builds [true/false]:
[false]: true
Whether to lock the Runner to current project [true/false]:
[true]: false
Please enter the executor:
docker
Please enter the default Docker image (e.g. ruby:2.1):
alpine:latest
Runner registered successfully.
В итоге в настройка появился новый runner.
git clone https://github.com/express42/reddit.git && rm -rf ./reddit/.git
git add reddit/
git commit -m “Add reddit app”
git push gitlab docker-6
image: ruby:2.4.2
stages:
- build
- test
- deploy
variables:
DATABASE_URL: 'mongodb://mongo/user_posts'
before_script:
- cd reddit
- bundle install
build_job:
stage: build
script:
- echo 'Building'
test_unit_job:
stage: test
services:
- mongo:latest
script:
- ruby simpletest.rb
test_integration_job:
stage: test
script:
- echo 'Testing 2'
deploy_job:
stage: deploy
script:
- echo 'Deploy'
require_relative './app'
require 'test/unit'
require 'rack/test'
set :environment, :test
class MyAppTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_get_request
get '/'
assert last_response.ok?
end
reddit\Gemfile
source 'https://rubygems.org'
gem 'sinatra'
gem 'haml'
gem 'bson_ext'
gem 'bcrypt'
gem 'puma'
gem 'mongo'
gem 'json'
gem 'rack-test'
group :development do
gem 'capistrano', require: false
gem 'capistrano-rvm', require: false
gem 'capistrano-bundler', require: false
gem 'capistrano3-puma', require: false
end
Теперь на каждое изменение в коде приложения будет запущен тест.
28. Пушим всё в ГитЛаб и набюдаем, как ревьюверы whitew1nd (Yury Ignatov) и postgred (Andrey Aleksandrov) активно фейспалмят!
1. Идём по ссылке https://devops-team-otus.slack.com/apps Ищем Incoming Webhook - выбираем Add Configuration
2. Выбираем канал, куда мы хотим получать сообщения или создаём новый канал, нажимаем Add WebHooks Integration
3. Меняем иконку на более удобоваримую Upload An Image. В поле Customize Name вводим имя пользователя, от которого будут приходить Эвенты. Копируем URL Webhook. Save Settings
https://hooks.slack.com/services/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
4. Заходим в проект Example - Settings - Integration - Slack Notifications. Кликаем галочку Active, в тригерах #Push указываем название комнаты, куда будет приходить уведомление #alexander-akilin
Вставляем наш webhook, указываем username - Test And save Changes. Радуемся пришедшим нотификашкам.
- Запустим контейнер с использованием none-драйвера, с временем жизни 100 секунд, по истечении автоматически удаляется. В качестве образа используем joffotron/docker-net-tools, в него входят утилиты bind-tools, net-tools и curl.
docker run --network none --rm -d --name net_test joffotron/docker-net-tools -c "sleep 100"
docker exec -ti net_test ifconfig
В результате, видим:
• что внутри контейнера из сетевых интерфейсов существует
только loopback.
• сетевой стек самого контейнера работает (ping localhost), но без
возможности контактировать с внешним миром.
• Значит, можно даже запускать сетевые сервисы внутри такого
контейнера, но лишь для локальных экспериментов
(тестирование, контейнеры для выполнения разовых задач и
т.д.)
- Запустили контейнер в сетевом пространстве docker-хоста
docker run --network host --rm -d --name net_test joffotron/docker-net-tools -c "sleep 100"
- Вывод команды docker exec -ti net_test ifconfig
br-9847ceaf8390 Link encap:Ethernet HWaddr 02:42:1A:94:FA:64
inet addr:172.18.0.1 Bcast:172.18.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:1aff:fe94:fa64%32531/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1731 errors:0 dropped:0 overruns:0 frame:0
TX packets:1795 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:145258 (141.8 KiB) TX bytes:283122 (276.4 KiB)
docker0 Link encap:Ethernet HWaddr 02:42:27:F2:53:24
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:27ff:fef2:5324%32531/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:18460 errors:0 dropped:0 overruns:0 frame:0
TX packets:28983 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1440883 (1.3 MiB) TX bytes:346814939 (330.7 MiB)
ens4 Link encap:Ethernet HWaddr 42:01:0A:84:00:02
inet addr:10.132.0.2 Bcast:10.132.0.2 Mask:255.255.255.255
inet6 addr: fe80::4001:aff:fe84:2%32531/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1460 Metric:1
RX packets:146266 errors:0 dropped:0 overruns:0 frame:0
TX packets:112334 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:908455582 (866.3 MiB) TX bytes:254880160 (243.0 MiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1%32531/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:151209 errors:0 dropped:0 overruns:0 frame:0
TX packets:151209 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:20506793 (19.5 MiB) TX bytes:20506793 (19.5 MiB)
veth09faf80 Link encap:Ethernet HWaddr 92:7D:D2:89:4A:BA
inet6 addr: fe80::907d:d2ff:fe89:4aba%32531/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:20324 errors:0 dropped:0 overruns:0 frame:0
TX packets:10393 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1922371 (1.8 MiB) TX bytes:3072704 (2.9 MiB)
veth70277fa Link encap:Ethernet HWaddr 5E:74:D5:EE:84:5B
inet6 addr: fe80::5c74:d5ff:feee:845b%32531/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:35274 errors:0 dropped:0 overruns:0 frame:0
TX packets:70262 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:10678546 (10.1 MiB) TX bytes:6665213 (6.3 MiB)
vethda70fe7 Link encap:Ethernet HWaddr 9E:6E:BB:55:B3:17
inet6 addr: fe80::9c6e:bbff:fe55:b317%32531/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:49921 errors:0 dropped:0 overruns:0 frame:0
TX packets:25008 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:4742482 (4.5 MiB) TX bytes:7615475 (7.2 MiB)
vethe3618d9 Link encap:Ethernet HWaddr A2:15:EC:B9:20:A0
inet6 addr: fe80::a015:ecff:feb9:20a0%32531/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:70 errors:0 dropped:0 overruns:0 frame:0
TX packets:110 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:15456 (15.0 KiB) TX bytes:14755 (14.4 KiB)
Вывод команды docker exec -ti net_test ifconfig
br-9847ceaf8390 Link encap:Ethernet HWaddr 02:42:1a:94:fa:64
inet addr:172.18.0.1 Bcast:172.18.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:1aff:fe94:fa64/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1731 errors:0 dropped:0 overruns:0 frame:0
TX packets:1795 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:145258 (145.2 KB) TX bytes:283122 (283.1 KB)
docker0 Link encap:Ethernet HWaddr 02:42:27:f2:53:24
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:27ff:fef2:5324/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:18460 errors:0 dropped:0 overruns:0 frame:0
TX packets:28983 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1440883 (1.4 MB) TX bytes:346814939 (346.8 MB)
ens4 Link encap:Ethernet HWaddr 42:01:0a:84:00:02
inet addr:10.132.0.2 Bcast:10.132.0.2 Mask:255.255.255.255
inet6 addr: fe80::4001:aff:fe84:2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1460 Metric:1
RX packets:146332 errors:0 dropped:0 overruns:0 frame:0
TX packets:112405 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:908469183 (908.4 MB) TX bytes:254894834 (254.8 MB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:151209 errors:0 dropped:0 overruns:0 frame:0
TX packets:151209 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:20506793 (20.5 MB) TX bytes:20506793 (20.5 MB)
veth09faf80 Link encap:Ethernet HWaddr 92:7d:d2:89:4a:ba
inet6 addr: fe80::907d:d2ff:fe89:4aba/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:20364 errors:0 dropped:0 overruns:0 frame:0
TX packets:10413 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1926171 (1.9 MB) TX bytes:3078804 (3.0 MB)
veth70277fa Link encap:Ethernet HWaddr 5e:74:d5:ee:84:5b
inet6 addr: fe80::5c74:d5ff:feee:845b/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:35344 errors:0 dropped:0 overruns:0 frame:0
TX packets:70402 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:10699896 (10.6 MB) TX bytes:6678513 (6.6 MB)
vethda70fe7 Link encap:Ethernet HWaddr 9e:6e:bb:55:b3:17
inet6 addr: fe80::9c6e:bbff:fe55:b317/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:50021 errors:0 dropped:0 overruns:0 frame:0
TX packets:25058 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:4751982 (4.7 MB) TX bytes:7630725 (7.6 MB)
vethe3618d9 Link encap:Ethernet HWaddr a2:15:ec:b9:20:a0
inet6 addr: fe80::a015:ecff:feb9:20a0/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:70 errors:0 dropped:0 overruns:0 frame:0
TX packets:110 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:15456 (15.4 KB) TX bytes:14755 (14.7 KB)
Видим, что значения совпадают, кроме того, что в первом случае сетевуха называется s4, во втором ens4
-
Запустили docker run --network host -d nginx несколько раз, а контейнер всё равно один запущен. Это фишка, а не баг.
-
На docker-host машине выполнили команду:
> sudo ln -s /var/run/docker/netns /var/run/netns
Теперь можно просматривать существующие неймспейсы с помощью
> sudo ip netns
• Теперь наше приложение состоит из трех компонент:
• post-py - сервис отвечающий за написание постов
• comment - сервис отвечающий за написание комментариев
• ui - веб-интерфейс, работающий с другими сервисами
FROM python:3.6.0-alpine
WORKDIR /app
ADD . /app
RUN pip install -r /app/requirements.txt
ENV POST_DATABASE_HOST post_db
ENV POST_DATABASE posts
ENTRYPOINT ["python3", "post_app.py"]
FROM ruby:2.2
RUN apt-get update -qq && apt-get install -y build-essential
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install
COPY . $APP_HOME
ENV COMMENT_DATABASE_HOST comment_db
ENV COMMENT_DATABASE comments
CMD ["puma"]
FROM ruby:2.2
RUN apt-get update -qq && apt-get install -y build-essential
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install
ADD . $APP_HOME
ENV POST_SERVICE_HOST post
ENV POST_SERVICE_PORT 5000
ENV COMMENT_SERVICE_HOST comment
ENV COMMENT_SERVICE_PORT 9292
CMD ["puma"]
> docker pull mongo:latest
> docker build -t <your-dockerhub-login>/post:1.0 ./post-py
> docker build -t <your-dockerhub-login>/comment:1.0 ./comment
> docker build -t <your-dockerhub-login>/ui:1.0 ./ui
Сборка ЮИ началась не с первого шага, потому как предыдущие шаги уже были проделаны при старте Каммента Памятка:
Что мы сделали? • Создали bridge-сеть для контейнеров, так как сетевые алиасы не работают в сети по умолчанию ( о сетях в Docker мы еще поговорим на следующем занятии) • Запустили наши контейнеры в этой сети • Добавили сетевые алиасы контейнерам • Сетевые алиасы могут быть использованы для сетевых соединений, как доменные имена
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y ruby-full ruby-dev build-essential \
&& gem install bundler --no-ri --no-rdoc
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install
ADD . $APP_HOME
ENV POST_SERVICE_HOST post
ENV POST_SERVICE_PORT 5000
ENV COMMENT_SERVICE_HOST comment
ENV COMMENT_SERVICE_PORT 9292
CMD ["puma"]
И пометили его как версия 2
> docker build -t <your-login>/ui:2.0 ./ui
> docker kill $(docker ps -q)
> docker run -d --network=reddit \
--network-alias=post_db --network-alias=comment_db mongo:latest
> docker run -d --network=reddit \
--network-alias=post <your-dockerhub—login>/post:1.0
> docker run -d --network=reddit \
--network-alias=comment <your-dockerhub-login>/comment:1.0
> docker run -d --network=reddit \
-p 9292:9292 <your-dockerhub-login>/ui:2.0
> docker volume create reddit_db
и подключим его к базе данных, Выключив предварительно старые копии контейнеров
> docker kill $(docker ps -q)
> docker run -d --network=reddit —network-alias=post_db \
--network-alias=comment_db -v reddit_db:/data/db mongo:latest
> docker run -d --network=reddit \
--network-alias=post <your-login>/post:1.0
> docker run -d --network=reddit \
--network-alias=comment <your-login>/comment:1.0
> docker run -d --network=reddit \
-p 9292:9292 <your-login>/ui:2.0
####1. Создали новый проект в GCP docker-194414 и создали докер хост:
> docker-machine create --driver google \
--google-project docker-194414 \
--google-zone europe-west1-b \
--google-machine-type g1-small \
--google-machine-image $(gcloud compute images list --filter ubuntu-1604-lts --uri) \
docker-host
docker-machine ls
eval $(docker-machine env docker-host)
- Dockerfile - текстовое описание нашего образа
FROM ubuntu:16.04 # Создаём имидж из последней убунты
RUN apt-get update # Обновили пакеты
RUN apt-get install -y mongodb-server ruby-full ruby-dev build-essential git # Установили монго и руби
RUN gem install bundler
RUN git clone https://github.com/Artemmkin/reddit.git # Скачали в контейнер реддит
# Копируем в контейнер файлы конфигурации
COPY mongod.conf /etc/mongod.conf
COPY db_config /reddit/db_config
COPY start.sh /start.sh
# Устанавливаем зависимости и запускаем скрипт настройки
RUN cd /reddit && bundle install
RUN chmod 0777 /start.sh
# Стартуем сервис при старте контейнера
CMD ["/start.sh"]
• mongod.conf - преподготовленный конфиг для mongodb
# Where and how to store data.
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1
• db_config - содержит переменную со ссылкой на mongodb
DATABASE_URL=127.0.0.1
• start.sh - скрипт запуска приложения
#!/bin/bash
/usr/bin/mongod --fork --logpath /var/log/mongod.log --config /etc/mongodb.conf
source /reddit/db_config
cd /reddit && puma || exit
docker build -t reddit:latest .
docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 6d5f15dea66c 24 minutes ago 682MB
asomir/otus-reddit 1.0 3ddd4d70e49f 24 minutes ago 682MB
reddit latest 3ddd4d70e49f 24 minutes ago 682MB
<none> <none> 33961ea15acd 24 minutes ago 682MB
<none> <none> fdd30df927aa 24 minutes ago 650MB
<none> <none> 1b07d9acf2a1 24 minutes ago 650MB
<none> <none> fd5c5ee9c266 24 minutes ago 650MB
<none> <none> 99eb7073e4ab 24 minutes ago 650MB
<none> <none> 29feb728d310 24 minutes ago 650MB
<none> <none> b21f1e029915 24 minutes ago 647MB
<none> <none> 28f07a2ab0c6 26 minutes ago 151MB
ubuntu 16.04 0458a4468cbc 2 weeks ago 112MB
docker run --name reddit -d --network=host reddit:latest
gcloud compute firewall-rules create reddit-app \
--allow tcp:9292 --priority=65534 \
--target-tags=docker-machine \
--description="Allow TCP connections" \
--direction=INGRESS
9. Пошли по ссылке http://35.205.170.223:9292/ - порадовались!
docker login
docker tag reddit:latest asomir/otus-reddit:1.0
docker push asomir/otus-reddit:1.0
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
asomir/ubuntu-tmp-file latest 46a62528372b 9 hours ago 112MB
asomir/nginx-tmp-file latest 5ecfc7c8efbd 9 hours ago 108MB
ubuntu 16.04 0458a4468cbc 10 days ago 112MB
ubuntu latest 0458a4468cbc 10 days ago 112MB
nginx latest 3f8a4339aadd 5 weeks ago 108MB
hello-world latest f2a91732366c 2 months ago 1.85kB
- Дз со свездой Выполним команду docker inspect 3f8a4339aadd > docker_image и docker inspect 979cef3539af > docker_container и сравним их вывод. Когда мы инспектим контейнер, то видим там описани его состояния, настроек сети :
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 3850,
"ExitCode": 0,
"Error": "",
"StartedAt": "2018-02-05T05:34:44.361548395Z",
"FinishedAt": "0001-01-01T00:00:00Z"
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:03",
"DriverOpts": null
Когда мы инспектим Image, мы видим архитектуру
"Architecture": "amd64",
"Os": "linux",
"Size": 108492271,
"VirtualSize": 108492271,
"GraphDriver": {
Config нашёлся в обоих местах image:
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"80/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NGINX_VERSION=1.13.8-1~stretch",
"NJS_VERSION=1.13.8.0.1.15-1~stretch"
],
"Cmd": [
"nginx",
"-g",
"daemon off;"
],
container
"Config": {
"Hostname": "979cef3539af",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"80/tcp": {}
},
"Tty": true,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NGINX_VERSION=1.13.8-1~stretch",
"NJS_VERSION=1.13.8.0.1.15-1~stretch"
],
"Cmd": [
"nginx",
"-g",
"daemon off;"
],
Итак, делаем вывод, что контейнер имеет состояние, конфигурацию, маунты, конфиг. В имидже также присутствует конфиг, метаинформация и информация об архитектуре.