В первом посте – Kubernetes: знакомство, часть 1 — архитектура и основные компоненты, обзор – были рассмотрены основные компонены, теперь время применить их на практике.
Продолжение – Kubernetes: знакомство, часть 3 — обзор AWS EKS и ручное создание кластера.
Следующим, что очень хотелось потрогать – это интеграция Kubernetes с AWS и работа с сетью: создать веб-сервис, и получить к нему доступ через AWS Load Balancer.
Главная проблема, с которой столкнулся во время настройки AWS cloud-provider в Kubernetes – это, снова-таки, отсутствие внятной документации и up to date примеров, поэтому пришлось долго и нудно делать практически методом тыка.
А потом читать в логе, что:
WARNING: aws built-in cloud provider is now deprecated. The AWS provider is deprecated and will be removed in a future release
Но так как сейчас это делается только для того, что бы “потыкать Kubernetes веточкой” – то особо роли не играет.
Используемая в примерах Kubernetes версия: v1.15.2., система на ЕС2 – Ubuntu 18.04
Содержание
Подготовка AWS
VPC
Создаём VPC с блоком 10.0.0.0/16:
Добавляем тег kubernetes.io/cluster/kubernetes со значением owned – он будет использоваться K8s для auto-discovery ресурсов AWS, относящихся к самому Kubenets, и этот же тег будет добавляться на создаваемых им сами ресурсах:
Включаем DNS hostnames:
Подсеть
Создаём подсеть в этой VPC:
Включаем публичные IP для EC2-инстансов, которые будут создаваться в этой сети:
Добавляем тег:
Internet Gateway
Создаём IGW, что бы роутить трафик из подсети в Интернет:
Для IGW на всякий случай добавляем тег тоже:
И подключаем этот IGW к нашей VPC:
Route Table
Создаём таблицу маршрутизации:
Тоже добавляем тег, тут он точно нужен:
Кликаем вкладку Routes, добавляем маршрут к сети 0.0.0.0/0 через созданный ранее IGW:
Подключаем таблицу к подсети – Edit subnet association:
Подключаем к созданной ранее подсети:
IAM роли
Для работы Kubernetes с AWS требуется добавить две IAM-роли – для мастера и для воркер-ноды (можно и через ACCESS/SECRET ключи).
IAM Master role
Переходим в IAM > Policies, жмём Create policy, в JSON добавляем политику (см. cloud-provider-aws):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "autoscaling:DescribeAutoScalingGroups", "autoscaling:DescribeLaunchConfigurations", "autoscaling:DescribeTags", "ec2:DescribeInstances", "ec2:DescribeRegions", "ec2:DescribeRouteTables", "ec2:DescribeSecurityGroups", "ec2:DescribeSubnets", "ec2:DescribeVolumes", "ec2:CreateSecurityGroup", "ec2:CreateTags", "ec2:CreateVolume", "ec2:ModifyInstanceAttribute", "ec2:ModifyVolume", "ec2:AttachVolume", "ec2:AuthorizeSecurityGroupIngress", "ec2:CreateRoute", "ec2:DeleteRoute", "ec2:DeleteSecurityGroup", "ec2:DeleteVolume", "ec2:DetachVolume", "ec2:RevokeSecurityGroupIngress", "ec2:DescribeVpcs", "elasticloadbalancing:AddTags", "elasticloadbalancing:AttachLoadBalancerToSubnets", "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer", "elasticloadbalancing:CreateLoadBalancer", "elasticloadbalancing:CreateLoadBalancerPolicy", "elasticloadbalancing:CreateLoadBalancerListeners", "elasticloadbalancing:ConfigureHealthCheck", "elasticloadbalancing:DeleteLoadBalancer", "elasticloadbalancing:DeleteLoadBalancerListeners", "elasticloadbalancing:DescribeLoadBalancers", "elasticloadbalancing:DescribeLoadBalancerAttributes", "elasticloadbalancing:DetachLoadBalancerFromSubnets", "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", "elasticloadbalancing:ModifyLoadBalancerAttributes", "elasticloadbalancing:RegisterInstancesWithLoadBalancer", "elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer", "elasticloadbalancing:AddTags", "elasticloadbalancing:CreateListener", "elasticloadbalancing:CreateTargetGroup", "elasticloadbalancing:DeleteListener", "elasticloadbalancing:DeleteTargetGroup", "elasticloadbalancing:DescribeListeners", "elasticloadbalancing:DescribeLoadBalancerPolicies", "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTargetHealth", "elasticloadbalancing:ModifyListener", "elasticloadbalancing:ModifyTargetGroup", "elasticloadbalancing:RegisterTargets", "elasticloadbalancing:SetLoadBalancerPoliciesOfListener", "iam:CreateServiceLinkedRole", "kms:DescribeKey" ], "Resource": [ "*" ] } ] }
Сохраняем её:
Переходим в Roles, создаём роль для EC2:
Жмём Permissions, находим и подключаем созданную выше политику:
IAM Worker role
Аналогично – создаём политику для воркеров:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:DescribeInstances", "ec2:DescribeRegions", "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:GetRepositoryPolicy", "ecr:DescribeRepositories", "ecr:ListImages", "ecr:BatchGetImage" ], "Resource": "*" } ] }
Сохраняем её как k8s-cluster-iam-worker-policy (имя любое, главное – понятное):
И создаём роль k8s-cluster-iam-master-role:
Запуск EC2
Создаём t2.medium EC2 (минимальный тип для Мастер-ноды, т.к. требуется минимум 2 ядра) в созданной VPC, подключаем созданную выше IAM роль k8s-cluster-iam-master-role:
Добавляем теги:
Создаём Security Group:
Пока запускается Мастер – аналогично создаём Воркер-ноду, только с ролью k8s-cluster-iam-worker-role:
Задаём теги:
Подключаем уже созданную SG:
Подключаемся к любому из них, проверяем работу сети:
[simterm]
$ ssh -i k8s-cluster-eu-west-3-key.pem ubuntu@35.***.***.117 'ping -c 1 1.1.1.1' PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data. 64 bytes from 1.1.1.1: icmp_seq=1 ttl=54 time=1.08 ms
[/simterm]
Работает.
Создание Kubernetes кластера
Установка Kubernetes
Выполняем на обеих машинах.
Обновляем систему:
[simterm]
root@ip-10-0-0-112:~# apt update && apt -y upgrade
[/simterm]
Добавляем репозитории Docker и Kubernetes:
[simterm]
root@ip-10-0-0-112:~# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - OK root@ip-10-0-0-112:~# add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" root@ip-10-0-0-112:~# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - OK root@ip-10-0-0-112:~# echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list root@ip-10-0-0-112:~# apt update root@ip-10-0-0-112:~# apt install -y docker-ce kubelet kubeadm kubectl
[/simterm]
Или всё сразу одной командой:
[simterm]
root@ip-10-0-0-112:~# apt update && apt -y upgrade && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - && add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - && echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list && apt update && apt install -y docker-ce kubelet kubeadm kubectl
[/simterm]
Hostname
Выполняем на обеих машинах.
Смена имени хоста нужна вроде как только на образах с Ubuntu, при этом нельзя менять дефолтное имя, которое задаётся AWS (в данном примере – ip-10-0-0-102).
Проверяем имя сейчас:
[simterm]
root@ip-10-0-0-102:~# hostname ip-10-0-0-102
[/simterm]
Получаем полное имя (FQDN):
[simterm]
root@ip-10-0-0-102:~# curl http://169.254.169.254/latest/meta-data/local-hostname ip-10-0-0-102.eu-west-3.compute.internal
[/simterm]
Задаём имя хоста в виде FQDN:
[simterm]
root@ip-10-0-0-102:~# hostnamectl set-hostname ip-10-0-0-102.eu-west-3.compute.internal
[/simterm]
Проверяем:
[simterm]
root@ip-10-0-0-102:~# hostname ip-10-0-0-102.eu-west-3.compute.internal
[/simterm]
Повторяем на воркере.
Создание кластера
На мастере создаём файл настроек /etc/kubernetes/aws.yml
:
--- apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration networking: serviceSubnet: "10.100.0.0/16" podSubnet: "10.244.0.0/16" apiServer: extraArgs: cloud-provider: "aws" controllerManager: extraArgs: cloud-provider: "aws"
Создаём кластер:
[simterm]
root@ip-10-0-0-102:~# kubeadm init --config /etc/kubernetes/aws.yml [init] Using Kubernetes version: v1.15.2 [preflight] Running pre-flight checks [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/ [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 19.03.1. Latest validated version: 18.09 ... [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env" [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml" [kubelet-start] Activating the kubelet service [certs] Using certificateDir folder "/etc/kubernetes/pki" [certs] Generating "ca" certificate and key [certs] Generating "apiserver" certificate and key [certs] apiserver serving cert is signed for DNS names [ip-10-0-0-102.eu-west-3.compute.internal kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.100.0.1 10.0.0.102] ... [kubeconfig] Using kubeconfig folder "/etc/kubernetes" [kubeconfig] Writing "admin.conf" kubeconfig file [kubeconfig] Writing "kubelet.conf" kubeconfig file [kubeconfig] Writing "controller-manager.conf" kubeconfig file [kubeconfig] Writing "scheduler.conf" kubeconfig file [control-plane] Using manifest folder "/etc/kubernetes/manifests" [control-plane] Creating static Pod manifest for "kube-apiserver" [control-plane] Creating static Pod manifest for "kube-controller-manager" [control-plane] Creating static Pod manifest for "kube-scheduler" [etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests" [wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s [apiclient] All control plane components are healthy after 23.502303 seconds [upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace [kubelet] Creating a ConfigMap "kubelet-config-1.15" in namespace kube-system with the configuration for the kubelets in the cluster ... [mark-control-plane] Marking the node ip-10-0-0-102.eu-west-3.compute.internal as control-plane by adding the label "node-role.kubernetes.io/master=''" [mark-control-plane] Marking the node ip-10-0-0-102.eu-west-3.compute.internal as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule] ... Your Kubernetes control-plane has initialized successfully! ... Then you can join any number of worker nodes by running the following on each as root: kubeadm join 10.0.0.102:6443 --token rat2th.qzmvv988e3pz9ywa \ --discovery-token-ca-cert-hash sha256:ce983b5fbf4f067176c4641a48dc6f7203d8bef972cb9d2d9bd34831a864d744
[/simterm]
Создаём файл настроек kubelet
:
[simterm]
root@ip-10-0-0-102:~# mkdir -p $HOME/.kube root@ip-10-0-0-102:~# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config root@ip-10-0-0-102:~# chown ubuntu:ubuntu $HOME/.kube/config
[/simterm]
Проверяем ноды:
[simterm]
root@ip-10-0-0-102:~# kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME ip-10-0-0-102.eu-west-3.compute.internal NotReady master 55s v1.15.2 10.0.0.102 <none> Ubuntu 18.04.3 LTS 4.15.0-1044-aws docker://19.3.1
[/simterm]
Конфигурацию кластера можно получить через config view
:
[simterm]
root@ip-10-0-0-102:~# kubeadm config view apiServer: extraArgs: authorization-mode: Node,RBAC cloud-provider: aws timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta2 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: extraArgs: cloud-provider: aws dns: type: CoreDNS etcd: local: dataDir: /var/lib/etcd imageRepository: k8s.gcr.io kind: ClusterConfiguration kubernetesVersion: v1.15.2 networking: dnsDomain: cluster.local podSubnet: 10.244.0.0/16 serviceSubnet: 10.100.0.0/16 scheduler: {}
[/simterm]
kubeadm reset
При необходимости сбросить все настройки кластера – испольуйте reset
:
[simterm]
root@ip-10-0-0-102:~# kubeadm reset
[/simterm]
И сбрасываем правила IPTABLES;
[simterm]
root@ip-10-0-0-102:~# iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X
[/simterm]
Установка Flannel CNI
С Мастера вызываем:
[simterm]
root@ip-10-0-0-102:~# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml podsecuritypolicy.policy/psp.flannel.unprivileged created clusterrole.rbac.authorization.k8s.io/flannel created clusterrolebinding.rbac.authorization.k8s.io/flannel created serviceaccount/flannel created configmap/kube-flannel-cfg created daemonset.apps/kube-flannel-ds-amd64 created daemonset.apps/kube-flannel-ds-arm64 created daemonset.apps/kube-flannel-ds-arm created daemonset.apps/kube-flannel-ds-ppc64le created daemonset.apps/kube-flannel-ds-s390x created
[/simterm]
Через минуту проверяем статус ещё раз:
[simterm]
root@ip-10-0-0-102:~# kubectl get nodes NAME STATUS ROLES AGE VERSION ip-10-0-0-102.eu-west-3.compute.internal Ready master 3m26s v1.15.2
[/simterm]
STATUS == Ready
, Okay.
Подключение Worker Node
На воркере создаём файл /etc/kubernetes/node.yml
с JoinConfiguration
:
--- apiVersion: kubeadm.k8s.io/v1beta1 kind: JoinConfiguration discovery: bootstrapToken: token: "rat2th.qzmvv988e3pz9ywa" apiServerEndpoint: "10.0.0.102:6443" caCertHashes: - "sha256:ce983b5fbf4f067176c4641a48dc6f7203d8bef972cb9d2d9bd34831a864d744" nodeRegistration: name: ip-10-0-0-186.eu-west-3.compute.internal kubeletExtraArgs: cloud-provider: aws
Подключаем ноду к кластеру:
[simterm]
root@ip-10-0-0-186:~# kubeadm join --config /etc/kubernetes/node.yml [preflight] Running pre-flight checks [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/ [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 19.03.1. Latest validated version: 18.09 [preflight] Reading configuration from the cluster... [preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml' [kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.15" ConfigMap in the kube-system namespace [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml" [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env" [kubelet-start] Activating the kubelet service [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap... ...
[/simterm]
Возвращаемся на мастер, проверяем ноды:
[simterm]
root@ip-10-0-0-102:~# kubectl get nodes NAME STATUS ROLES AGE VERSION ip-10-0-0-102.eu-west-3.compute.internal Ready master 7m37s v1.15.2 ip-10-0-0-186.eu-west-3.compute.internal Ready <none> 27s v1.15.2
[/simterm]
Создание Load Balancer
И последний шаг – создать какой-то веб-сервис, пусть будет простой под с NGINX, и перед ним – Service типа LoadBalancer
:
kind: Service apiVersion: v1 metadata: name: hello spec: type: LoadBalancer selector: app: hello ports: - name: http protocol: TCP # ELB's port port: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: hello spec: replicas: 1 selector: matchLabels: app: hello template: metadata: labels: app: hello spec: containers: - name: hello image: nginx
Создаём их:
[simterm]
root@ip-10-0-0-102:~# kubectl apply -f elb-example.yml service/hello created deployment.apps/hello created
[/simterm]
Проверяем Deployment
:
[simterm]
root@ip-10-0-0-102:~# kubectl get deploy -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR hello 1/1 1 1 22s hello nginx app=hello
[/simterm]
[simterm]
root@ip-10-0-0-102:~# kubectl get rs -o wide NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR hello-5bfb6b69f 1 1 1 39s hello nginx app=hello,pod-template-hash=5bfb6b69f
[/simterm]
Сам Pod:
[simterm]
root@ip-10-0-0-102:~# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES hello-5bfb6b69f-4pklx 1/1 Running 0 62s 10.244.1.2 ip-10-0-0-186.eu-west-3.compute.internal <none> <none>
[/simterm]
И Services:
[simterm]
root@ip-10-0-0-102:~# kubectl get svc -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR hello LoadBalancer 10.100.102.37 aa5***295.eu-west-3.elb.amazonaws.com 80:30381/TCP 83s app=hello kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 17m <none>
[/simterm]
Проверяем ELB в AWS:
Инстансы – тут наша Worker-нода:
Вспомним, как работает вся эта связка:
- AWS ELB направляет трафик на Worker ноду Kubernetes-кластера (
NodePort
Service) - на Worker node через сервис
NodePort
трафик роутится на порт пода (TargetPort
) - на самом поде с
TargetPort
трафик напрвялется уже на порт контейнера (containerPort
)
В описании LoadBalancer в консоли AWS видно, что:
Port Configuration
80 (TCP) forwarding to 30381 (TCP)
Проверяем сервис Kubernetes:
[simterm]
root@ip-10-0-0-102:~# kk describe svc hello Name: hello Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"hello","namespace":"default"},"spec":{"ports":[{"name":"http","po... Selector: app=hello Type: LoadBalancer IP: 10.100.102.37 LoadBalancer Ingress: aa5***295.eu-west-3.elb.amazonaws.com Port: http 80/TCP TargetPort: 80/TCP NodePort: http 30381/TCP Endpoints: 10.244.1.2:80 ...
[/simterm]
Наш NodePort
: http 30381/TCP
Можем обратиться к нему напрямую.
Находим адрес ноды:
[simterm]
root@ip-10-0-0-102:~# kk get node | grep -v master NAME STATUS ROLES AGE VERSION ip-10-0-0-186.eu-west-3.compute.internal Ready <none> 51m v1.15.2
[/simterm]
И подключаемся на порт 30381:
[simterm]
root@ip-10-0-0-102:~# curl ip-10-0-0-186.eu-west-3.compute.internal:30381 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ...
[/simterm]
Проверяем работу ELB:
[simterm]
root@ip-10-0-0-102:~# curl aa5***295.eu-west-3.elb.amazonaws.com <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ...
[/simterm]
Логи пода:
[simterm]
root@ip-10-0-0-102:~# kubectl logs hello-5bfb6b69f-4pklx 10.244.1.1 - - [09/Aug/2019:13:57:10 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
[/simterm]
AWS Load Balancer – no Worker Node added
Пока создавался рабочий вариант – несколько раз сталкивался с тем, что Worker-ноды не подключались к ELB.
Помогла проверка ProviderID
(--provider-id
).
Проверяем воркер-ноду:
[simterm]
root@ip-10-0-0-102:~# kubectl describe node ip-10-0-0-186.eu-west-3.compute.internal | grep ProviderID ProviderID: aws:///eu-west-3a/i-03b04118a32bd8788
[/simterm]
Если ProviderID
нет – можно задать его вручную через kubectl edit node <NODE_NAME>
в виде ProviderID: aws:///eu-west-3a/<EC2_INSTANCE_ID>
:
Но вообще он задаётся с помощью указания cloud-provider: aws
в файле настроек ноды /etc/kubernetes/node.yml
в JoinConfiguration.
Готово.
Ссылки по теме
Common
AWS
- Kubernetes Cloud Controller Manager
- Cloud Providers
- How to create a k8s cluster with kubeadm on Azure
- Setting up the Kubernetes AWS Cloud Provider
- Using Kubeadm to Add New Control Plane Nodes with AWS Integration
- Kubernetes Cluster Setup with AWS Cloud Provider
- Kubernetes on AWS
- Rancher: AWS Cloud Provider
LoadBalancer, network
- Services of type LoadBalancer and Multiple Ingress Controllers
- Kubernetes and AWS ELB — What to do when you reach the Security Group limit in AWS
- Using Kubernetes LoadBalancer Services on AWS
- Deploy an app behind a Load Balancer on Kubernetes
- Load Balancing and Reverse Proxying for Kubernetes Services
- How to Add Load Balancers to Kubernetes Clusters
- AWS ALB Ingress Controller for Kubernetes