Настройка отказоустойчивого кластера Kubernetes на серверах с публичной и приватной сетью с помощью Kubeadm

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Эта статья написана потому, что я бы хотел иметь такую статью перед глазами, когда развертывал кластер по документации. Сразу хочу сказать, что не являюсь экспертом в K8S, однако имел опыт с развертыванием продуктовых установок DC/OS (экосистемы, основанной на Apache Mesos). Долгое время K8S меня отпугивал тем, что, при попытке его изучения, тебя закидывают кучей концепций и терминов, отчего мозг взрывается.

Тем не менее, у меня возникла задача настроить отказоустойчивый Bare Metal кластер для комплексного приложения, в связи с чем и возникла данная статья. В процессе руководства я затрону следующие аспекты:

  • корректная установка с помощью kubeadm на узлах с несколькими NIC;

  • реализация отказоустойчивого Control Plane с доступом по общему IP и DNS-имени;

  • реализация Ingress контроллера на базе Nginx на выделенных узлах с доступом из публичной сети;

  • проброс K8S API в публичную сеть;

  • проброс K8S Dashboard UI в публичную сеть.

Я выполнял установку в среде Ubuntu 18.04, в связи с чем часть из шагов может не работать в вашем дистрибутиве. В тексте могут встречаться фразы "как я понял..." и "я не до сих пор не вполне понял...".

Сначала рассмотрим узловую топологию кластера, в котором мы будем развертывать K8S. Это упрощенная принципиальная топология, чтобы не загромождать публикацию лишними деталями.

Отличительной особенностью в моем кластере является то, что у всех узлов имеется два сетевых интерфейся - в моем случае, на eth0 всегда находится публичный адрес, а на eth1 - адрес из сети 10.120.0.0/16.

Стоит отметить, что K8S исключительно проще настраивать в случае, если ваши машины имеют по одному NIC. Если ваша инфраструктура позволяет вам использовать машины с одним сетевым устройством и иметь внешний балансировщик нагрузки - однозначно так необходимо делать.

В случае моей же инфраструктуры такая топология хотя и возможна, но я понял насколько K8S становится проще при использовании машин с одной NIC уже после того, как начал развертывание, поэтому решил "победить" проблемы, в рамках данной топологии, сохранив преимущества доступа к каждой машине напрямую.

Еще, kubeadm при развертывании считает, что что правильный IP-адрес - это тот, который находится в сети, где шлюз "по-умолчанию", если не переопределить, ничего хорошего не выйдет.

Тем не менее, в случае использования Enterprise решений, машины с несколькими сетевыми картами и доступом к ним из разных сетей - достаточно рядовая история, поэтому данная модель развертывания имеет право на существование.

Хочу отметить, что я использую 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-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-серверы и поисковые домены. Вместо этого будут использоваться серверы 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.cluster, ping gw-2.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 для применения изменений.

Установка containerd

Выполняется на всех узлах. Kubernetes рекомендует использовать containerd (впрочем, новые версии docker тоже используют containerd), поэтому установим его:

sudo apt-get update
sudo apt install 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

Инициализация первого узла K8S

Выполняется на узлах, которые будут обслуживать 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)'

Теперь можно скопировать настройки конфигурации для доступа администратора к кластеру в домашний каталог:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Для проверки запросите задачи, выполняемые на данном настроенном контроллере посредством команды 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. В моем случае было три варианта:

  1. Keepalived;

  2. Pacemaker;

  3. 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.yml
kubectl apply -f cluster_config/vip-2.yml
kubectl apply -f cluster_config/vip-3.yml

В результате вы должны получить три нормально выполняющихся POD-а, каждый на своем узле, при этом адрес 10.120.0.1 должен нормально пинговаться. Проверьте, что только один из kube-vip владеет IP:

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-1, он будет соединяться по k8s-cp, но использовать уже другой IP-адрес.

На данном этапе у нас есть реализация отказоустойчивого 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: Deployment
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
spec:
  replicas: 2
  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
    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:

Вводим токен, полученный с помощью:

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. Надеюсь, что данное руководство поможет вам сэкономить свое время.

Источник: https://habr.com/ru/post/540220/


Интересные статьи

Интересные статьи

Прим. перев.: казалось бы, еще недавно сочетание из заголовка казалось невозможным безумием. Мир не стоит на месте, и это безумие стало не просто возможным, а даже по-настоящему простым в...
Уже давольно давно, вдохновившись статьей Ресайз изображений на лету был настроен ресайз изображений с помощью ngx_http_image_filter_module и все работало как надо. Но появилась одна проблема, ко...
В прошлой статье мы описали эксперимент по определению минимального объема вручную размеченных срезов для обучения нейронной сети на данных сейсморазведки. Сегодня мы продолжаем эту тему, выбирая...
Возможно ли это? Да. Знаю, звучит странно и больше похоже на упражнение в бесполезности, но это не так. В этой статье я расскажу, как можно использовать С++ в разработке сайта, и приведу нес...
Корни цифрового звука уходят глубоко в 70-е годы. Тогда японская телевизионная компания NHK проспонсировала производство PCM-рекордеров, которые задействовали во время эфиров. Уже позже с расп...