В первом посте — 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-кластера (
NodePortService) - на 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





































