Эта статья написана потому, что я бы хотел иметь такую статью перед глазами, когда развертывал кластер по документации. Сразу хочу сказать, что не являюсь экспертом в K8S, однако имел опыт с развертыванием продуктовых установок DC/OS (экосистемы, основанной на Apache Mesos). Долгое время K8S меня отпугивал тем, что, при попытке его изучения, тебя закидывают кучей концепций и терминов, отчего мозг взрывается.
Тем не менее, у меня возникла задача настроить отказоустойчивый Bare Metal кластер для комплексного приложения, в связи с чем и возникла данная статья. В процессе руководства я затрону следующие аспекты:
корректная установка K8S с помощью kubeadm на узлах с несколькими NIC;
реализация отказоустойчивого Control Plane с доступом по общему IP и DNS-имени;
реализация Ingress контроллера на базе Nginx на выделенных узлах с доступом из публичной сети;
проброс K8S API в публичную сеть;
доступ к K8S Dashboard UI с компьютера администратора.
Я выполнял установку в среде Ubuntu 18.04, в связи с чем часть из шагов может не работать в вашем дистрибутиве, плюс у меня возникли специфические проблемы с Keepalived, Pacemaker. В тексте могут встречаться фразы "как я понял..." и "я не до сих пор не вполне понял...".
Сначала рассмотрим узловую топологию кластера, в котором мы будем развертывать K8S. Это упрощенная топология, без лишних деталей.
Отличительной особенностью в моем кластере является то, что у всех узлов имеется два сетевых интерфейся - на eth0 всегда находится публичный адрес, а на eth1
- адрес из сети 10.120.0.0/16
.
Стоит отметить, что K8S исключительно проще настраивать в том случае, когда машины имеют по одному NIC. Если ваша инфраструктура позволяет вам использовать машины с одним сетевым устройством и иметь внешний балансировщик нагрузки - однозначно так необходимо делать.
В случае моей инфраструктуры такая топология хотя и в принципе возможна, но я понял насколько K8S проще настроить при использовании машин с одной NIC уже после того, как начал развертывание, поэтому решил "победить" проблемы, в рамках текущей топологии, сохранив преимущества доступа к каждой машине напрямую из связанных сетей.
Kubeadm при развертывании служб считает, что что правильный IP-адрес - это тот, который находится в той же сети, где шлюз "по-умолчанию", если это поведение не переопределить, ничего хорошего не выйдет.
В случае использования Enterprise решений с сегментами вне управления K8S, машины с несколькими сетевыми картами и доступом к ним из разных сетей - достаточно рядовая история, поэтому данная модель развертывания вполне может встретиться, к примеру, Ceph может предоставляться узлам по отдельной высокоскоростной сети или VLAN-у.
Хочу отметить, что свой кластер я развертывал с помощью Ansible, но в качестве упрощения не буду в статье демонстрировать playbook-и, ориентируясь на настройку руками. Итак, приступим.
Замена DNS-рекурсора
Я хочу обеспечить доступность всех узлов кластера через DNS-имена по внутренним IP-адресам, сохранив доступность разрешения обычных имен узлов в интернете. Для этого, на серверах gw-1
, gw-2
я разверну pdns-recursor и укажу его в качестве рекурсора на всех узлах кластера.
Базовая настройка pdns-recursor на gw-1
, gw-2
включает указание следующих директив:
allow-from=10.120.0.0/8, 127.0.0.0/8
etc-hosts-file=/etc/hosts.resolv
export-etc-hosts=on
export-etc-hosts-search-suffix=cluster
Сам файл /etc/hosts.resolv
генерируется с помощью ansible и выглядит следующим образом:
# Ansible managed
10.120.29.231 gw-1 gw-1
10.120.28.23 gw-2 gw-2
10.120.29.32 video-accessors-1 video-accessors-1
10.120.29.226 video-accessors-2 video-accessors-2
10.120.29.153 mongo-1 mongo-1
10.120.29.210 mongo-2 mongo-2
10.120.29.220 mongo-3 mongo-3
10.120.28.172 compute-1 compute-1
10.120.28.26 compute-2 compute-2
10.120.29.70 compute-3 compute-3
10.120.28.127 zk-1 zk-1
10.120.29.110 zk-2 zk-2
10.120.29.245 zk-3 zk-3
10.120.28.21 minio-1 minio-1
10.120.28.25 minio-2 minio-2
10.120.28.158 minio-3 minio-3
10.120.28.122 minio-4 minio-4
10.120.29.187 k8s-1 k8s-1
10.120.28.37 k8s-2 k8s-2
10.120.29.204 k8s-3 k8s-3
10.120.29.135 kafka-1 kafka-1
10.120.29.144 kafka-2 kafka-2
10.120.28.130 kafka-3 kafka-3
10.120.29.194 clickhouse-1 clickhouse-1
10.120.28.66 clickhouse-2 clickhouse-2
10.120.28.61 clickhouse-3 clickhouse-3
10.120.29.244 app-1 app-1
10.120.29.228 app-2 app-2
10.120.29.33 prometeus prometeus
10.120.29.222 manager manager
10.120.29.187 k8s-cp
Шаблон Ansible для генерации конфига
# {{ ansible_managed }}
{% for item in groups['all'] %}
{% set short_name = item.split('.') %}
{{ hostvars[item]['host'] }} {{ item }} {{ short_name[0] }}
{% endfor %}
10.120.0.1 k8s-cp
Далее необходимо сделать так, чтобы все узлы вместо DNS-рекурсоров, получаемых из настроек DHCP, использовали данные DNS-ы. В Ubuntu 18.04 используется systemd-resolved
, поэтому необходимо подсунуть ему требуемые серверы gw-1, gw-2
. Для этого создадим на каждом хосте кластера конфигурационный файл systemd, переопределяющий поведение systemd-resolved
, разместив его по пути /etc/systemd/network/0-eth0.network
:
[Match]
Name=eth0
[Network]
DHCP=ipv4
DNS=10.120.28.23 10.120.29.231
Domains=cluster
[DHCP]
UseDNS=false
UseDomains=false
Делает он следующее: для инструкций DHCP, полученных через eth0 будут игнорироваться DNS-серверы и поисковые домены, определенные на DHCP-сервере. Вместо этого будут использоваться серверы 10.120.28.23, 10.120.29.231
и поисковый домен *.cluster
.
После создания данного файла требуется перезагрузить узел или сетевую подсистему узла, поскольку простой перезапуск systemd-resolved
не инициирует повторное получение данных по DHCP. Я перезагружаю хост полностью для того, чтобы убедиться в корректном поведении при старте узла.
При успешной инициализации systemd-resolve --status
выдаст следующий листинг:
Global
DNSSEC NTA: 10.in-addr.arpa
16.172.in-addr.arpa
168.192.in-addr.arpa
17.172.in-addr.arpa
18.172.in-addr.arpa
19.172.in-addr.arpa
20.172.in-addr.arpa
21.172.in-addr.arpa
22.172.in-addr.arpa
23.172.in-addr.arpa
24.172.in-addr.arpa
25.172.in-addr.arpa
26.172.in-addr.arpa
27.172.in-addr.arpa
28.172.in-addr.arpa
29.172.in-addr.arpa
30.172.in-addr.arpa
31.172.in-addr.arpa
corp
d.f.ip6.arpa
home
internal
intranet
lan
local
private
test
Link 3 (eth1)
Current Scopes: none
LLMNR setting: yes
MulticastDNS setting: no
DNSSEC setting: no
DNSSEC supported: no
Link 2 (eth0)
Current Scopes: DNS
LLMNR setting: yes
MulticastDNS setting: no
DNSSEC setting: no
DNSSEC supported: no
DNS Servers: 10.120.28.23
10.120.29.231
DNS Domain: cluster
Это действие необходимо выполнить на всех узлах кластера. При корректном выполнении каждый узел сможет выполнить ping gw-1
, ping gw-1.cluster
и получить ответ от данных узлов по внутренним ip-адресам.
Отключение раздела подкачки
Выполняется на всех узлах. Kubernetes не хочет работать при наличии разделов подкачки на узлах. Для их отключения вы можете воспользоваться следующим однострочником:
sudo -- sh -c "swapoff -a && sed -i '/ swap / s/^/#/' /etc/fstab"
Для пущей уверенности удалите swap-раздел с помощью fdisk
.
Внесение изменений в сетевые настройки ядра
Выполняется на всех узлах. Я буду использовать Flannel - простейший оверлейный сетевой плагин для K8S. Обратите внимание, что довольно много сетевых плагинов используют VXLAN
. Это накладывает определенные особенности для сетевой инфраструктуры.
В простейшем случае, вам требуется обеспечить работоспособность multicast
, поскольку VXLAN
использует multicast
-группы, для своей работы. Если multicast
- не ваш вариант, но вы хотите использовать провайдер, основанный на VXLAN
, можно настроить работу VXLAN
через BGP или другими способами. Однако, сможет ли жить с этим выбранный вами провайдер сетевой инфраструктуры Kubernetes - это большой вопрос. В общем, Flannel поддерживает VXLAN
через multicast. В моем случае это VXLAN+multicast
over VXLAN+multicast
over Ethernet
, поскольку в моей сети виртуальные машины имеют VXLAN-бэкбон
, работающий поверх Ethernet
с использованием multicast
- так тоже работает.
В /etc/modules добавьте br_netfilter, overlay
.
Выполните modprobe br_netfilter && modprobe overlay
, чтобы загрузить модули.
В /etc/sysctl.conf
добавьте:
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
Выполните sysctl -p
для применения изменений.
Установка Сontainerd
Выполняется на всех узлах. Kubernetes рекомендует использовать Containerd (впрочем, новые версии Docker тоже используют Containerd), поэтому установим его:
sudo apt-get update
sudo apt install containerd
sudo mkdir -p /etc/containerd
sudo -- sh -c "containerd config default | tee /etc/containerd/config.toml"
sudo service containerd restart
Установим kubeadm, kubelet, kubectl
Выполняется на всех узлах. Здесь прям из руководства по установке K8S:
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
Инициализация узлов Control Plane
Выполняется на узлах, которые будут обслуживать Control Plane K8S - в моем случае k8s-{1,2,3}
. Здесь уже есть нюансы, специфичные для моего развертывания:
kubeadm init --pod-network-cidr=10.244.0.0/16 \
--control-plane-endpoint=k8s-cp \
--apiserver-advertise-address=10.120.29.187
Для Flannel категорически важно использовать --pod-network-cidr=10.244.0.0/16
. С другим адресным пространством для POD-ов K8S он не запустится.
--control-plane-endpoint string Specify a stable IP address or DNS name for the control plane.
Здесь Вы должны указать тот DNS или IP, который будет использоваться для связи всех шурушков K8S-а с Control Plane. Я решил использовать доменное имя k8s-cp
, привязанное к отказоустойчивому ip-адресу 10.120.0.1
(см. далее, на текущий момент, k8s-cp указывает на один из серверов Control Plane: 10.120.29.187 k8s-cp
).
Важный аргумент --api-server-advertise-address
. Он влияет не только на api-server
, но и на Etcd
, что нигде не описано, но принципиально важно для корректной работоспособности отказоустойчивой топологии. Если ничего не указать, то kubeadm
возьмет адрес с той сети, в которой находится шлюз по-умолчанию, что не всегда верно. В моем случае это приводит к тому, что Etcd
стартует на публичном интерфейсе, а кластер Etcd
хочет работать по публичной сети, что требует открытия дополнительных портов и создает возможность атаки на эту службу. Это меня не устраивает.
Если этот адрес не задать корректно, то Flannel тоже не сможет инициализироваться, будет падать с ошибками, что не может связаться с Control Plane (будет использовать тот же IP-адрес из сети со шлюзом по умолчанию для связи).
В общем, этот параметр привносит много геморроя и ведет к некорректной работе всего, что только может некорректно работать.
Теперь, если все указано верно, то Kubeadm
развернет K8S на данном узле. Я рекомендую выполнить перезагрузку узла для того, чтобы убедиться что Control Plane стартует как надо. Убедитесь в этом, запросив список выполняемых задач после перезагрузки узла:
ps xa | grep -E '(kube-apiserver|etcd|kube-proxy|kube-controller-manager|kube-scheduler)'
Теперь надо скопировать настройки конфигурации для доступа администратора к кластеру в домашний каталог. Это делается для того, чтобы утилита kubectl
работала с конфигурационным файлом по стандартному пути:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Для проверки работоспособности kubectl
запросите задачи, выполняемые на настроенном узле посредством команды kubectl get pods --all-namespaces
. Вы должны получить вывод, примерно соответствующий следующему:
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system etcd-k8s-1 1/1 Running 0 2d23h
kube-system kube-apiserver-k8s-1 1/1 Running 0 2d23h
kube-system kube-controller-manager-k8s-1 1/1 Running 1 2d23h
kube-system kube-scheduler-k8s-1 1/1 Running 1 2d23h
Я рекомендую посмотреть вывод этой команды еще пару раз, с перерывом 1 минуту, чтобы убедиться, что RESTARTS
не растут, а статус Running
.
Сам Kubernetes никакой сети не предоставляет, делегируя это плагинам CNI. Мы будем использовать простой CNI - Flannel. Его установка производится следующей командой:
kubectl apply -f https://github.com/coreos/flannel/raw/master/Documentation/kube-flannel.yml
Опять же, выполните kubectl get pods --all-namespaces
несколько раз, чтобы убедиться, что Flannel выполняется без ошибок и RESTARTS не растут. Если что-то пошло не так, посмотрите журнал событий Flannel следующим способом (только используйте настоящее имя POD-а Flannel):
kubectl logs -n kube-system kube-flannel-ds-xn2j9
Если ошибок нет, можно двигаться дальше. Теперь вы можете подключить два других узла Control Plane с помощью команд, выполненной на каждом из них:
# ssh k8s-2
sudo kubeadm join k8s-cp:6443 --apiserver-advertise-address=10.120.28.37 --token tfqsms.kiek2vk129tpf0b7 --discovery-token-ca-cert-hash sha256:0c446bfabcd99aae7e650d110f8b9d6058cac432078c4fXXXXX6055b4bd --control-plane
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# ssh k8s-3
sudo kubeadm join k8s-cp:6443 --apiserver-advertise-address=10.120.29.204 --token tfqsms.kiek2vk129tpf0b7 --discovery-token-ca-cert-hash sha256:0c446bfabcd99aae7e650d110f8b9d6058cac432078c4fXXXXXec6055b4bd --control-plane
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Если вдруг --token
"протух", используйте команду kubeadm token create
, чтобы сгенерировать новый.
После подключения узла смотрите внимательно за состоянием Etcd
:
kubectl get pods --all-namespaces | grep etcd
kube-system etcd-k8s-1 1/1 Running 0 2d23h
kube-system etcd-k8s-2 1/1 Running 1 2d22h
kube-system etcd-k8s-3 1/1 Running 1 2d22h
Состояние должно быть Running, увеличение счетчика перезагрузок не должно происходить. Команда kubectl get pods --all-namespaces
должна отображать трехкратный набор процессов на всех узлах Control Plane:
NAME READY STATUS RESTARTS AGE
coredns-74ff55c5b-h2zjq 1/1 Running 0 2d23h
coredns-74ff55c5b-n6b49 1/1 Running 0 2d23h
etcd-k8s-1 1/1 Running 0 2d23h
etcd-k8s-2 1/1 Running 1 2d22h
etcd-k8s-3 1/1 Running 1 2d22h
kube-apiserver-k8s-1 1/1 Running 0 2d23h
kube-apiserver-k8s-2 1/1 Running 1 2d22h
kube-apiserver-k8s-3 1/1 Running 1 2d22h
kube-controller-manager-k8s-1 1/1 Running 1 2d23h
kube-controller-manager-k8s-2 1/1 Running 1 2d22h
kube-controller-manager-k8s-3 1/1 Running 1 2d22h
kube-flannel-ds-2f6d5 1/1 Running 0 2d3h
kube-flannel-ds-2p5vx 1/1 Running 0 2d3h
kube-flannel-ds-4ng99 1/1 Running 3 2d22h
kube-proxy-22jpt 1/1 Running 0 2d3h
kube-proxy-25rxn 1/1 Running 0 2d23h
kube-proxy-2qp8r 1/1 Running 0 2d3h
kube-scheduler-k8s-1 1/1 Running 1 2d23h
kube-scheduler-k8s-2 1/1 Running 1 2d22h
kube-scheduler-k8s-3 1/1 Running 1 2d22h
Следующим шагом настроим отказоустойчивый IP-адрес, который будет использоваться для доступа к Control Plane.
Отказоустойчивый IP и DNS имя
В моем случае было три варианта:
Keepalived;
Pacemaker;
kube-vip.
Здесь я потратил достаточно много времени, чтобы все завести. Начал я с Keepalived
, который прекрасно работает в других моих серверных системах - в общем-то, я планировал им и закончить. Однако, он у меня не завелся, не знаю в чем проблема - в Ubuntu 18.04 или в VXLAN
-сети, которую я использую в качестве Underlay. Tcpdump
показывал веселые VRRP
-пакетики, летящие между k8s-{1,2,3}
, но IP-адрес вешался всеми 3мя узлами, все считали себя MASTER
-ами. Поскольку debug ничего полезного не дал, я отказался от Keepalived
и выполнил настройку на Pacemaker
. Тут меня ждала очередная неприятность - Corosync
и Pacemaker
не взлетали самостоятельно при старте, несмотря на выполненный ритуал:
sudo systemctl enable corosync
sudo systemctl enable pacemaker
Я придерживаюсь такого мнения, что корневые компоненты или работают или не надо их использовать, в /etc/rc.local
прописывать эти службы не хотелось.
В итоге, я познакомился со специализированным решением по предоставлению отказоустойчивого IP-адреса kube-vip, разработанным специально для Kubernetes. Для его запуска нам понадобится создать три манифеста, каждый для запуска kube-vip на одном из узлов Control Plane:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
name: kube-vip-cp-k8s-1 # поменять имя, соответственно узлу
namespace: kube-system
spec:
nodeName: k8s-1 # будет запускаться именно на этом узле
containers:
- args:
- start
env:
- name: vip_arp
value: "true"
- name: vip_interface
value: eth1
- name: vip_leaderelection
value: "true"
- name: vip_leaseduration
value: "5"
- name: vip_renewdeadline
value: "3"
- name: vip_retryperiod
value: "1"
- name: vip_address
value: 10.120.0.1 # указать реальный IP, который будет использоваться
image: plndr/kube-vip:0.3.1 # проверить актуальную версию
imagePullPolicy: Always
name: kube-vip-cp
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
- SYS_TIME
volumeMounts:
- mountPath: /etc/kubernetes/admin.conf
name: kubeconfig
- mountPath: /etc/ssl/certs
name: ca-certs
readOnly: true
hostNetwork: true
volumes:
- hostPath:
path: /etc/kubernetes/admin.conf
name: kubeconfig
- hostPath:
path: /etc/ssl/certs
name: ca-certs
status: {}
Данный манифест необходимо сформировать для каждого из серверов Control Plane и применить все:
kubectl apply -f cluster_config/vip-{1,2,3}.yml
В результате вы должны получить три нормально выполняющихся POD-а, каждый на своем узле, при этом адрес 10.120.0.1
должен нормально пинговаться. Проверьте, что только один из kube-vip
владеет IP с помощью arping
:
sudo arping 10.120.0.1
ARPING 10.120.0.1
42 bytes from 1e:01:17:00:01:22 (10.120.0.1): index=0 time=319.476 usec
42 bytes from 1e:01:17:00:01:22 (10.120.0.1): index=1 time=306.360 msec
42 bytes from 1e:01:17:00:01:22 (10.120.0.1): index=2 time=349.666 usec
Чем хорош kube-vip? Он не только предоставляет отказоустойчивый IP, но и определяет когда сервер на хосте, где он выполняется, становится недоступен, переставая балансировать на него трафик.
Теперь, когда kube-vip предоставляет отказоустойчивый доступ к Kubernetes необходимо на хостах-рекурсорах gw-1
, gw-2
в /etc/hosts.resolv
обновить записи для k8s-cp
:
10.120.0.1 k8s-cp
Выполните перезагрузку pdns-recursor командой sudo service pdns-recursor restart
и проверьте, что k8s-cp
отвечает со всех узлов IP адресом 10.120.0.1
. Проверьте, что kubectl
все еще корректно работает с новой записью для k8s-cp
.
На данном этапе у нас есть реализация отказоустойчивого Control Plane K8S. Я рекомендую несколько раз поочередно перезагружать k8s-{1,2,3}
, чтобы проверить, что кластер остается в работоспособном состоянии.
Добавление узла Worker-а
В нашей топологии предполагается использование двух узлов gw-1
, gw-2
, на которых будет размещен Nginx Ingress и один узел общего назначения (compute-1
).
Все эти узлы в кластер можно добавить следующим образом:
kubeadm token create --print-join-command
kubeadm join k8s-cp:6443 --token rn0s5p.y6waq1t6y2y6z9vw --discovery-token-ca-cert-hash sha256:0c446bfabcd99aae7e650d110f8b9d6058cac432078c4fXXXe22ec6055b4bd
# ssh gw-1
sudo kubeadm join k8s-cp:6443 --token rn0s5p.y6waq1t6y2y6z9vw --discovery-token-ca-cert-hash sha256:0c446bfabcd99aae7e650d110f8b9d6058cac432078c4fXXXe22ec6055b4bd
# ssh gw-2
...
# ssh compute-1
После добавления kubectl get pds --all-namespaces
должен показать расширенный набор выполняющихся POD-ов, а kubectl get nodes
должен вывести все узлы кластера:
kubectl get nodes
NAME STATUS ROLES AGE VERSION
compute-1 Ready compute 2d23h v1.20.2
gw-1 Ready gateway 2d4h v1.20.2
gw-2 Ready gateway 2d4h v1.20.2
k8s-1 Ready control-plane,master 2d23h v1.20.2
k8s-2 Ready control-plane,master 2d23h v1.20.2
k8s-3 Ready control-plane,master 2d23h v1.20.2
Теперь можно задать узлам роли, для того, чтобы иметь возможность использовать их при развертывании приложений. В листинге выше роли уже заданы, поскольку он сделан с рабочего кластера.
Назначение ролей узлам
Роль можно присвоить просто:
kubectl label node gw-1 node-role.kubernetes.io/gateway=true
kubectl label node gw-2 node-role.kubernetes.io/gateway=true
kubectl label node compute-1 node-role.kubernetes.io/compute=true
# если надо удалить роль
kubectl label node compute-1 node-role.kubernetes.io/compute-
Настройка Ingress
Сейчас все готово для того, чтобы можно было выполнить развертывание Nginx Ingress на узлах gw-1
, gw-2
. Воспользуемся манифестом Nginx Ingress, но внесем в него ряд изменений:
запускать будем с сетью
hostNetwork
;запускать будем в виде Deployment с фактором масштабирования "2";
запускать будем на узлах с ролью
gateway
.
Разберем подробнее необходимость hostNetwork
. Дело в том, что при использовании K8S в рамках какого-то облачного провайдера, последний через API назначает каждому узлу External IP, который может быть использован приложениями для связи с внешним миром. Так вот, у нас в bare metal кластере никаких External IP нет:
kubectl get nodes --output wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
compute-1 Ready compute 3d v1.20.2 10.120.28.172 <none> Ubuntu 18.04.5 LTS 4.15.0-135-generic containerd://1.3.3
gw-1 Ready gateway 2d4h v1.20.2 10.120.29.231 <none> Ubuntu 18.04.5 LTS 4.15.0-135-generic containerd://1.3.3
gw-2 Ready gateway 2d4h v1.20.2 10.120.28.23 <none> Ubuntu 18.04.5 LTS 4.15.0-135-generic containerd://1.3.3
k8s-1 Ready control-plane,master 3d v1.20.2 10.120.29.187 <none> Ubuntu 18.04.5 LTS 4.15.0-135-generic containerd://1.3.3
k8s-2 Ready control-plane,master 2d23h v1.20.2 10.120.28.37 <none> Ubuntu 18.04.5 LTS 4.15.0-135-generic containerd://1.3.3
k8s-3 Ready control-plane,master 2d23h v1.20.2 10.120.29.204 <none> Ubuntu 18.04.5 LTS 4.15.0-135-generic containerd://1.3.3
Собственно, назначить этот External IP можно только через API, при этом он сбрасывается самим Kubernetes время от времени и требует постоянной установки. В общем, это неудобно и использоваться нормально не может.
Я читал длинную переписку на GitHub, которая закончилась ничем вразумительным, еще советуют использовать metallb, который тоже непонятно в каком состоянии. Как итог, я решил просто завести Nginx Ingress, используя hostNetworking
, поскольку это обеспечивает привязку данного Ingress с адресам 0.0.0.0:443
, 0.0.0.0:80
и решает мою задачу.
Собственно, манифест для запуска Nginx Ingress выглядит так:
Очень большой фрагмент YAML
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
---
# Source: ingress-nginx/templates/controller-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
data:
---
# Source: ingress-nginx/templates/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
name: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ''
resources:
- nodes
verbs:
- get
- apiGroups:
- ''
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io # k8s 1.14+
resources:
- ingressclasses
verbs:
- get
- list
- watch
---
# Source: ingress-nginx/templates/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
name: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- namespaces
verbs:
- get
- apiGroups:
- ''
resources:
- configmaps
- pods
- secrets
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io # k8s 1.14+
resources:
- ingressclasses
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- configmaps
resourceNames:
- ingress-controller-leader-nginx
verbs:
- get
- update
- apiGroups:
- ''
resources:
- configmaps
verbs:
- create
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
---
# Source: ingress-nginx/templates/controller-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-service-webhook.yaml
apiVersion: v1
kind: Service
metadata:
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller-admission
namespace: ingress-nginx
spec:
type: ClusterIP
ports:
- name: https-webhook
port: 443
targetPort: webhook
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
---
# Source: ingress-nginx/templates/controller-deployment.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
node-role.kubernetes.io/gateway: "true"
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
revisionHistoryLimit: 10
minReadySeconds: 0
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
node-role.kubernetes.io/gateway: "true"
spec:
hostNetwork: true
dnsPolicy: ClusterFirst
containers:
- name: controller
image: k8s.gcr.io/ingress-nginx/controller:v0.43.0@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
args:
- /nginx-ingress-controller
- --election-id=ingress-controller-leader
- --ingress-class=nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LD_PRELOAD
value: /usr/local/lib/libmimalloc.so
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 5
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
volumeMounts:
- name: webhook-cert
mountPath: /usr/local/certificates/
readOnly: true
resources:
requests:
cpu: 100m
memory: 90Mi
nodeSelector:
node-role.kubernetes.io/gateway: "true"
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
---
# Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml
# before changing this value, check the required kubernetes version
# https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisites
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
name: ingress-nginx-admission
webhooks:
- name: validate.nginx.ingress.kubernetes.io
matchPolicy: Equivalent
rules:
- apiGroups:
- networking.k8s.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- ingresses
failurePolicy: Fail
sideEffects: None
admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
namespace: ingress-nginx
name: ingress-nginx-controller-admission
path: /networking/v1beta1/ingresses
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
rules:
- apiGroups:
- admissionregistration.k8s.io
resources:
- validatingwebhookconfigurations
verbs:
- get
- update
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- secrets
verbs:
- get
- create
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: ingress-nginx-admission-create
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
spec:
template:
metadata:
name: ingress-nginx-admission-create
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
containers:
- name: create
image: docker.io/jettech/kube-webhook-certgen:v1.5.1
imagePullPolicy: IfNotPresent
args:
- create
- --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
- --namespace=$(POD_NAMESPACE)
- --secret-name=ingress-nginx-admission
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
serviceAccountName: ingress-nginx-admission
securityContext:
runAsNonRoot: true
runAsUser: 2000
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: ingress-nginx-admission-patch
annotations:
helm.sh/hook: post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
spec:
template:
metadata:
name: ingress-nginx-admission-patch
labels:
helm.sh/chart: ingress-nginx-3.21.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.43.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
containers:
- name: patch
image: docker.io/jettech/kube-webhook-certgen:v1.5.1
imagePullPolicy: IfNotPresent
args:
- patch
- --webhook-name=ingress-nginx-admission
- --namespace=$(POD_NAMESPACE)
- --patch-mutating=false
- --secret-name=ingress-nginx-admission
- --patch-failure-policy=Fail
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
serviceAccountName: ingress-nginx-admission
securityContext:
runAsNonRoot: true
runAsUser: 2000
Запустив его с помощью kubectl apply -f nginx-ingress.yaml
, мы получим два POD-а Nginx, выполняющихся на узлах gw-1
, gw-2
и слушающих 443-й и 80-й порты:
kubectl get pods --all-namespaces | grep nginx
ingress-nginx ingress-nginx-admission-create-4mm9m 0/1 Completed 0 46h
ingress-nginx ingress-nginx-admission-patch-7jkwg 0/1 Completed 2 46h
ingress-nginx ingress-nginx-controller-b966cf6cd-7kpzm 1/1 Running 1 46h
ingress-nginx ingress-nginx-controller-b966cf6cd-ckl97 1/1 Running 0 46h
На узлах gw-1
, gw-2
:
sudo netstat -tnlp | grep -E ':(443|80)'
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 2661/nginx: master
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 2661/nginx: master
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2661/nginx: master
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2661/nginx: master
tcp6 0 0 :::443 :::* LISTEN 2661/nginx: master
tcp6 0 0 :::443 :::* LISTEN 2661/nginx: master
tcp6 0 0 :::80 :::* LISTEN 2661/nginx: master
tcp6 0 0 :::80 :::* LISTEN 2661/nginx: master
Можно постучаться на публичные адреса gw-1
, gw-2
по 80-му порту и получить приветственную страницу Nginx. Далее, Вы можете использовать публичные адреса gw-1
, gw-2
для создания записей DNS, использования в CDN и т.п.
Протестировать работу inress можно, создав сервис, на который Ingress будет проксировать трафик (взято отсюда) - echo1.yaml
:
apiVersion: v1
kind: Service
metadata:
name: echo1
spec:
ports:
- port: 80
targetPort: 5678
selector:
app: echo1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo1
spec:
selector:
matchLabels:
app: echo1
replicas: 2
template:
metadata:
labels:
app: echo1
spec:
containers:
- name: echo1
image: hashicorp/http-echo
args:
- "-text=echo1"
ports:
- containerPort: 5678
Выполните данный манифест с помощью kubectl apply -f echo1.yaml
. Теперь создадим правило Ingress (ingress-echo1.yaml
):
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: echo-ingress
spec:
rules:
- host: echo1.example.com
http:
paths:
- backend:
serviceName: echo1
servicePort: 80
Выполним данный манифест kubectl apply -f ingress-echo1.yaml
. Теперь, если на локальном компьютере в /etchosts
внести запись для echo1.example.com
:
127.0.0.1 localhost
127.0.1.1 manager
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
X.Y.Z.C echo1.example.com
То можно получить проксирование трафика через Nginx Ingress. Проверим с помощью curl
:
curl echo1.example.com
echo1
Установка K8S Dashboard
Для установки Dashboard необходимо выполнить следующую команду:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml
Dashboard имеет ряд ограничений, например, он не работает через http
, если обращение осуществляется не с localhost
. В целом, не рекомендуется как-либо предоставлять доступ к Dashboard извне кластера через Ingress. Кроме того, Dashboard использует встроенную систему RBAC Kubernetes, поэтому требуется создать пользователя и дать ему права на Dashboard.
Инструкция взята с этой страницы. Создадим аккаунт:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
EOF
Определим роль пользователя:
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
EOF
Получим токен, с помощью которого пользователь сможет войти в Dashboard:
kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"
Если на вашей машине уже настроен kubect
и есть $HOME/.kube/config
, а сама машина "видит" k8s-cp
, то вы можете запустить kubectl proxy
и получить доступ к Dashboard по ссылке: http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/.
Если же ваша машина с браузером находится вне кластера, то переходим к следующему шагу, где мы настроим проброс API Kubernetes наружу через HAProxy.
Доступ к API K8S из внешней сети
На узлах gw-1
, gw-2
необходимо установить haproxy
. После установки измените конфигурационный файл /etc/haproxy/haproxy.cfg
так, чтобы далее секции defaults
он выглядел следующим образом:
defaults
# mode is inherited by sections that follow
mode tcp
frontend k8s
# receives traffic from clients
bind :6443
default_backend kubernetes
backend kubernetes
# relays the client messages to servers
server k8s k8s-cp:6443
Теперь вы можете обратиться к API K8S извне, указав в /etc/hosts
своей локальной машины адрес gw-1
или gw-2
в качестве k8s-cp
. Вам должны быть доступны с локальной машины все команды kubectl
, включая kubectl proxy
:
kubectl proxy
Starting to serve on 127.0.0.1:8001
После этого можно открыть в браузере http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ и насладиться видом приглашения авторизации Dashboard K8S:
Вводим токен для ранее созданного пользователя Dasboard, полученный с помощью:
kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"
eyJhbGciOiJSUzI1NiIsImtpZCI6IlFkcGxwMTN2YlpyNS1TOTYtUnVYdsfadfsdjfksdlfjm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLWd6anprIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJjM2RiOWFkMS0yYjdmLTQ3YTYtOTM3My1hZWI2ZjJkZjk0NTAiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.ht-RyqLXY4UzBnxIzJcnQn8Kk2iHWZzKVeAjUhKkJ-vOAtyqBu50rExgiRWsEHWfJp1p9xN8sXFKg62FFFbHWftPf6PmwfcBK2lXPy6OL9OaooP17WJVn75KKXIZHPWLO9VmRXPB1S-v2xFuG_J8jHB8pZHJlFjp4zIXfB--QwhrxeoTt5zv3CfXCl1_VYgCoqaxRsa7e892-vtMijBo7EZfJiyuAUQr_TsAIDY3zOWFJeHbwPFWzL_1fF9Y2o3r0pw7hYZHUoI8Z-3hbfOi10QBRyQlNZTMFSh7Z38RRbV1tw2ZmMvgSQyHa9TZFy2kYik6VnugNB2cilamo_b7hg
и переходим на главный экран Dashboard:
Вместо заключения
Как я писал в начале статьи, у меня уже есть опыт развертывания систем управления контейнерами на базе Docker, Apache Mesos (DC/OS). Тем не менее, документация Kubernetes мне показалась сложной, запутанной и фрагментарной. Чтобы получить текущий результат я прошерстил довольном много сторонних руководств, Issue GitHub и, конечно, документацию Kubernetes. Надеюсь, что данное руководство поможет вам сэкономить свое время.