Вступление
Многие знают про аттракцион необычайной щедрости от Oracle. В своем облаке они дают Always Free не только пару небольших машинок на AMD, но и мощный сервер на ARM. 4 vCPU и целых 24GB RAM!
Поскольку с ARM я раньше дела практически не имел (только Raspberry, но это другое), мне было интересно погонять на нем Kubernetes, посмотреть отличия, сильно ли сложнее искать образы для ARM и т.п.
Так что в этой статье расскажу основные моменты, с которыми столкнулся, где ошибался. И в качестве примера свяжу его с домом через Wireguard, настрою Nginx ingress controller + basic auth + LetsEncrypt, а также мониторинг на Grafana + VictoriaMetrics.
Установка кластера
Общее понимание, что можно получить бесплатно: Always Free Resources.
Создание аккаунта
Часто бывают жалобы типа «не проходит карточка». Где-то видел подсказку, что адрес нужно вводить в американском формате: НомерДома, Улица, apt. НомерКв.
Не знаю, насколько правда, но у меня действительно принялась виртуальная кредитка Tinkoff (привязанная к основной, но с принудительно очень ограниченным лимитом), которая раньше не проходила. Полгода назад я несколько карт перепробовал прежде, чем нашел подходящую. Теперь же зарегистрировалось с первого раза. Может быть их антифрод менее подозрительно относится к правильно выглядящим адресам…
И да, вы не ослышались. У меня уже полгода работает такой бесплатный сервер просто с Docker, а сейчас вот завел второй аккаунт (другой номер телефона, карты и ФИО, но тот же самый домашний IP) для экспериментов с Kubernetes.
Настройка сети
Не люблю предустановленные настройки, адреса по умолчанию… Первым делом удалил VCN и создал новый. А также две сети: Public (можно добавлять публичные IP) и Private (публичным такой ресурс уже не сделать, только через балансировщик и т.п.).
Для Public нужно создать Internet Gateway и настроить маршрут на него.
Для Private – создать NAT Gateway и для этой приватной сети указать маршрут в Интернет через него. В противном случае «внутри» все работать будет, виртуалка создастся, но даже apt update не пройдет. Даже нода хотя и создастся, на нее можно буджет зайти по ssh, но Ready она не станет.
Создание кластера
Containers & Artifacts | Create cluster. Опять же, Quick Create мне не нравится, я шел через Custom. Хотя вполне можно и Quick. Система создаст VCN, сети, IGW, но названия ресурсов будут некрасивые.
Здесь важно определиться, какой доступ к кластеру вы хотите. Проще всего, конечно, и Kubernetes API endpoint, и будущие ноды размещать в Public subnet. Я же выбрал делать все приватным и только Load Balancer в Public.
Когда дело дошло до Node pool, выбрал VM.Standard.A1.Flex (тот самый ARM). Указал 4 CPU и 24GB RAM и что нужна только 1 нода. Можно и 2 по 2CPU/12GB, но для меня это менее эффективно и нецелесообразно. Выбор образов небогатый, взял Oracle Linux 8.
Здесь я сначала задал Availability Domain = EU-FRANKFURT-1-AD-2, но система очень долго висела в состоянии «кластер создан, node pool тоже, но ни одной ноды нет». Тупо не было требуемых ARM ресурсов. Через кнопку Scale поменял availability domain на AD-1, и все очень быстро создалось.
Размер Boot Volume сначала указал 100GB (вместо штатных 46GB), но в OS виделось все равно как 46GB. Расширил потом до 100G, но почему-то у ноды появились taint node.kubernetes.io/unreachable:NoSchedule
. Удаление не помогало, перезагрузка тоже. Может временный сбой какой-то был (Scale до 2 создавал дополнительную ноду, но она оставлась NotReady), а может действительно нельзя так вмешиваться в работу managed кластера. Не стал разбираться, пересоздал со стандартным Boot volume.
Как расширял диск
fdisk /dev/sda
# удалил раздел 3, создал заново с того же места до максимума, Remove LVM2 signature – Нет, сохранил, вышел, перезгарузился на всякий случай.
pvresize /dev/sda3
lvextend -l +100%FREE /dev/ocivolume/root
Size of logical volume ocivolume/root changed from 35.47 GiB (9081 extents) to <88.90 GiB (22758 extents)
xfs_growfs /dev/ocivolume/root
Ах да, в Compute | Instances эта виртуалка будет видна без значка Always Free. Не пугайтесь, это нормально, потому что ограничивается общее количество CPU/RAM в месяц. При круглосуточной работе это те самые 4CPU/24GB. Мможно и несколькими маленькими набрать. Или наоборот, взять в 4 раза мощнее, но гонять только 1 неделю.
И на всякий случай про диск – в первом аккаунте был инцидент, что несмотря на общий объем менее 200GB, за один диск копеечку начислили (из бюджета € 250 которые даются на первые 30 дней). Оказалось, что я его создал, когда лимит был исчерпан, поэтому в Always Free он не попал. И не вернулся в него, когда потом я лишние удалил. Надо учитывать такую особенность.
Доступ
Поскольку извне никак в приватные сети не попасть, в Public subnet создал Compute | Instances. Ну а что, дают целых 2 штуки VM.Standard.E2.1.Micro (1 OCPU, 1GB RAM, 0.48Gbps). Тут я уже Ubuntu 20.04 заказал.
В Security group добавил полный доступ с публичных адресов домашней подсети, чтобы потом не заморачиваться отдельно с SSH, Wireguard и т.п.
Сеть
Конечно, было бы красиво установить IPSEC прямо в облако. Но у меня дома не то что публичного IP нет, я еще и за парой Hide NAT. Поэтому для связи с внешними ресурсами (облаками, VPS) я использую Wireguard еще со времен, когда он появился на Mikrotik в виде беты.
Для Ubuntu
sudo -i
apt install wireguard
cd /etc/wireguard
umask 077
wg genkey | tee privatekey | wg pubkey > publickey
cat <<EOF > wg0.conf
[Interface]
Address = 192.168.16.4/24
SaveConfig = true
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE;
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE
ListenPort = 51820
PrivateKey = XXX=
[Peer]
PublicKey = YYY=
AllowedIPs = 192.168.16.1/32, 192.168.4.0/22
EOF
Разрешить форвардинг пакетов между интерфейсами
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p
Важно: нужно поправить настройку VNIC у этой виртуальной машины.
И там Skip Source/Destination Check
Раньше нужно было перезагрузиться после установки. Сейчас вроде не обязательно, но не повредит.
wg-quick up wg0
dmesg -wT | grep wireguard
[Sun Feb 6 07:13:57 2022] wireguard: WireGuard 1.0.0 loaded. See www.wireguard.com for information.
wg-quick down wg0
Все работает, пусть будет автостарт
systemctl enable wg-quick@wg0.service
systemctl daemon-reload
systemctl start wg-quick@wg0
И нужно входящие UDP пакеты разрешить. И сохранить правила iptables.
sudo iptables -I INPUT 1 -i ens3 -p udp --dport 51820 -j ACCEPT
sudo /sbin/iptables-save | sudo tee /etc/iptables/rules.v4
После этого туннель поднимется (если «дома» все уже настроено), но только для этой VM. Чтобы стали доступны Kubernetes Endpoint и сама нода в этой приватной сети, нужно поправить таблицу маршрутизации. К «все в Интернет через NAT gateway» добавить «192.168 (которые я использую в примерах) на приватный адрес этой Ubuntu». Если забыли поправить Skip src check, на этом шаге вам напомнят.
Ну и нужно разрешить форвардинг между ens3 и wg0. Для разнообразия поправим непосредственно /etc/iptables/rules.v4, добавив пару правил
:InstanceServices - [0:0]
-A INPUT -i ens3 -p udp -m udp --dport 51820 -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p udp -m udp --sport 123 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
# Add this
-A FORWARD -i ens3 -o wg0 -j ACCEPT
-A FORWARD -i wg0 -o ens3 -j ACCEPT
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
Теперь из дома можно подключаться к ARM ноде по ее приватному адресу. Равно как и к Kubernetes API endpoint.
Управление Kubernetes
Ниже я покажу упрощенный вариант, но на этой виртуальной Ubuntu я делал все стандартно
Установка oci cli
sudo apt install python3-pip
sudo pip install oci-cli
Поскольку на Windows машине у меня уже был .oci/config с ключами, я его просто перенес на Ubuntu.
Поправил key_file=C:\Users\user\.oci\sessions\DEFAULT\oci_api_key.pem
на key_file=/home/ubuntu/.oci/sessions/DEFAULT/oci_api_key.pem
и пофиксил разрешения oci setup repair-file-permissions --file /home/ubuntu/.oci/config
Дальше стандартно
ClusterID=ocid1.cluster.oc1.eu-frankfurt-1.aaa…….
oci ce cluster create-kubeconfig --cluster-id $ClusterID --file $HOME/.kube/config --region eu-frankfurt-1 --token-version 2.0.0 --kube-endpoint PRIVATE_ENDPOINT
kubectl
kubectl можно стандартно через apt поставить. Я просто скачал для унификации с установкой на Oracle Linux ARM.
RELEASE="v1.21.5"
ARCH="amd64" # "arm64" – для запуска на ноде
sudo curl -L --remote-name-all https://storage.googleapis.com/kubernetes-release/release/${RELEASE}/bin/linux/${ARCH}/kubectl
sudo chmod +x kubectl
sudo install -o root -g root -m 0755 kubectl /usr/bin/kubectl
source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> $HOME/.bashrc
Еще люблю смотреть поды с IP адресами и нодой, но не люблю лишние колонки READINESS GATES и т.п., поэтому в .bashrc обычно добавляю функцию (а не alias, который не принимает аргументы типа namespace)
kgpod() { kubectl get po -o wide -o=custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,READY:.status.containerStatuses[0].ready,RESTARTS:.status.containerStatuses[0].restartCount,IP:.status.podIP,NODE:.status.hostIP $*; }
Итак, kubectl работает. На облачной Ubuntu вполне прилично, а вот с домашней винды довольно долго отвечает. И это понятно – по умолчанию аутентификация выполняется через плагин. Т.е. запускается нечто, меня аутентифицирует в облаке по ранее сохраненнм ключам… Прямо на глаз задержки видны. Да и на другие линуксовые машинки (а у меня в планах связать кластеры) не хочется ставить oci cli.
Поэтому я переключился на «по старинке» через токен. При том, что кластер доступен только изнутри через Wireguard, не считаю это существенным снижением безопасности.
ServiceAccount
Как из bash сделать, и так понятно. Покажу, как это делал с Windows машины из powershell (в частности, для декодирования токена вместо base64 используется штатный certutil).
kubectl -n kube-system create serviceaccount sa-user
kubectl create clusterrolebinding sa-user-bind --clusterrole=cluster-admin --serviceaccount=kube-system:sa-user
$TOKENNAME=kubectl -n kube-system get serviceaccount/sa-user -o jsonpath='{.secrets[0].name}'
kubectl -n kube-system get secret $TOKENNAME -o jsonpath='{.data.token}' > token.tmp
certutil -decode token.tmp token
$TOKEN=cat token
kubectl config set-credentials sa-user --token=$TOKEN
kubectl config set-context --current --user=sa-user
Теперь и откликаться система стала намного быстрее, и на другие машины достаточно перенести .kube/config
Если файл с другим именем, просто указываем, какой именно нужен.
export KUBECONFIG=~/.kube/ociK8s
Monitoring
Подготовку закончили, пора уже что-то полезное развернуть.
Ingress Controller
Я, конечно, начал с Nginx Ingress Controller. При этом автоматически создался Network Load Balancer. Кстати, при большом желании на нем можно прокинуть TCP/6443 на kube-api и TCP/22 на ноду. Но это не спортивно.
Если захочется поработать непосредственно с ноды, можно helm и для ARM поставить
https://github.com/helm/helm/releases
wget https://get.helm.sh/helm-v3.8.0-linux-arm64.tar.gz
sudo install -o root -g root -m 0755 kubectl /usr/bin/kubectl
sudo install -o root -g root -m 0755 linux-arm64/helm /usr/bin/helm
rm -rf linux-arm64
Но я уже все дальше с домашней VM делал
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm search repo ingress
NAME CHART VERSION APP VERSION DESCRIPTION
ingress-nginx/ingress-nginx 4.0.16 1.1.1 Ingress controller for Kubernetes using NGINX a...
Для системных подов и мониторинга создам namespace, в него же установлю nginx
kubectl create ns sysmon # to install system and monitoring tools
helm install -n sysmon ingress-nginx ingress-nginx/ingress-nginx
Кстати, в результатах работы helm приводится не совсем корректный пример ingress. В нем отсутствует pathType
, без него будут ругательства.
Metrics-server
Без него даже kubectl top pod -A не покажет
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.0/components.yaml
И нужно отредактировать конфигурацию (добавить выделенную строчку, у меня же все на самоподписанных сертифкатах)
kubectl -n kube-system edit deployment metrics-server
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-insecure-tls # ADD
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
Victoria Metrics
Устанавливаем
Значительно быстрее Prometheus, поддерживает кучу всего другого. Например, принимает данные также и от telegraf / InfluxDB. В общем, очень нравится.
Тут уже нужно думать, где хранить данные. Я рассчитывал на встроенные провизионеры oracle.com/oci, blockvolume.csi.oraclecloud.com. Но при попытке запросить PVC 1Gi автоматически был создан том 50GB. Типа это минимум. Спасибо, не надо.
Можно поднять NFS на виртуалке вместе с Wireguard. Наверняка так и сделаю для других задач. Но если телеметрия пропадет, не страшно. свободные 30 гигов на ноде жальче :) Решил это хранить локально.
sudo mkdir -p /mnt/LoSt/vicmet # Local Storage :)
cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: server-volume-vicmet-victoria-metrics-single-server-0
labels:
type: local
spec:
storageClassName: "local"
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/LoSt/vicmet"
EOF
Для PV указал длинное имя в точности, как будет назван PVC при развертывании helm chart. При совпадении имен они автоматически свяжутся.
helm repo add vm https://victoriametrics.github.io/helm-charts/
helm repo update
helm search repo vm/victoria-metrics-single -l | head -5
helm show values vm/victoria-metrics-single > vm_values_single.yaml
Это посмотреть параметры и указать ниже:
cat <<EOF | helm install vicmet vm/victoria-metrics-single -n sysmon -f -
server:
persistentVolume:
# -- Persistent volume annotations
annotations:
volume.beta.kubernetes.io/storage-class: "local"
# -- Storage class name. Will be empty if not setted
storageClassName: local
size: 5Gi
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 500m
memory: 512Mi
scrape:
enabled: true
configMap: ""
extraLabels:
appname: victoriametrics
EOF
Настройки скрапинга хранятся в configmap. Он создается автоматически, дополнить своими можно:
kubectl edit configmaps -n sysmon vicmet-victoria-metrics-single-server-scrapeconfig
scrape_configs:
- job_name: victoriametrics
static_configs:
- targets:
- localhost:8428
# Свой фрагмент
- job_name: node-XXX
static_configs:
- targets: ['XXX.spec.antn.in:19100']
- job_name: kubernetes-apiservers
Подключаемся
Временно опубликую через ingress. Только не простой, а с basic authentication.
htpasswd -c auth vicmet
и дважды указываю пароль
kubectl create secret generic basic-auth-vicmet --from-file=auth -n sysmon
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vicmet
namespace: sysmon
annotations:
# type of authentication
nginx.ingress.kubernetes.io/auth-type: basic
# name of the secret that contains the user/password definitions
nginx.ingress.kubernetes.io/auth-secret: basic-auth-vicmet
# message to display with an appropriate context why the authentication is required
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - Victoria Metrics'
spec:
ingressClassName: nginx
rules:
- host: vicmet.antn.in
http:
paths:
- backend:
service:
name: vicmet-victoria-metrics-single-server
port:
number: 8428
path: /
pathType: ImplementationSpecific
EOF
После обновления DNS (CNAME на LoadBalancer IP, кучи IP не надо, бесплатно только 6 дают) http://vicmet.antn.in спрашивает пароль и потом позволяет посмотреть таргеты, метрики…
Grafana
Еще немного осталось :)
Grafana я установлю не через helm, а просто подготовленным yaml.
Длинный, поэтому спойлер
sudo mkdir -p /mnt/LoSt/grafana
sudo chown 472 /mnt/LoSt/grafana
Без этого grafana не сможет писать в директорию и не запустится.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: grafana-pv
labels:
type: local
spec:
storageClassName: "local"
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/LoSt/grafana"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: grafana-pv
namespace: sysmon
labels:
app: grafana
spec:
storageClassName: "local"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: grafana
name: grafana
namespace: sysmon
spec:
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
securityContext:
fsGroup: 472
supplementalGroups:
- 0
containers:
- name: grafana
image: grafana/grafana:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
name: http-grafana
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /robots.txt
port: 3000
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 2
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 3000
timeoutSeconds: 1
resources:
requests:
cpu: 250m
memory: 750Mi
limits:
cpu: 500m
memory: 1000Mi
volumeMounts:
- mountPath: /var/lib/grafana
name: grafana-pv
volumes:
- name: grafana-pv
persistentVolumeClaim:
claimName: grafana-pv
---
apiVersion: v1
kind: Service
metadata:
name: grafana
namespace: sysmon
spec:
ports:
- port: 3000
protocol: TCP
targetPort: http-grafana
selector:
app: grafana
sessionAffinity: None
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grafana
namespace: sysmon
spec:
ingressClassName: nginx
rules:
- host: grafana.antn.in
http:
paths:
- backend:
service:
name: grafana
port:
number: 3000
path: /
pathType: ImplementationSpecific
EOF
LetsEncrypt
И финальный аккорд – настроим https с валидными сертификатами.
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.7.1/cert-manager.yaml
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging # <--
spec:
acme:
# You must replace this email address with your own.
# Let's Encrypt will use this to contact you about expiring
# certificates, and issues related to your account.
email: cert@antn.in
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# Secret resource that will be used to store the account's private key.
name: letsencrypt-staging
# Add a single challenge solver, HTTP01 using nginx
solvers:
- http01:
ingress:
class: nginx
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod # <--
spec:
acme:
# You must replace this email address with your own.
# Let's Encrypt will use this to contact you about expiring
# certificates, and issues related to your account.
email: cert@antn.in
server: https://acme-v02.api.letsencrypt.org/directory # <--
privateKeySecretRef:
# Secret resource that will be used to store the account's private key.
name: letsencrypt-prod
# Add a single challenge solver, HTTP01 using nginx
solvers:
- http01:
ingress:
class: nginx
EOF
Сначала проверил на staging, потом переключился на prod. Из-за кеширования в браузере удобнее проверять в incognito mode. Еще можен понадобиться удалить прежний сертифкат (kubectl delete secret -n sysmon grafana-tls)
Соответственно обновленный ingress, теперь с сертификатом. Просто помимо секции tls добавили аннотацию, «призывающую» cert-manager.
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grafana
namespace: sysmon
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod" # <--
spec:
ingressClassName: nginx
tls:
- hosts:
- grafana.antn.in
secretName: grafana-tls
rules:
- host: grafana.antn.in
http:
paths:
- backend:
service:
name: grafana
port:
number: 3000
path: /
pathType: ImplementationSpecific
EOF
Все подключилось. Добавил источник данных (тип prometheus, адрес/URL с именем сервиса, который мне helm chart victoria-metrics выдал)
Добавил первую попавшуюся на сайте графаны Dashboard ID 747 и при импорте указал вышенастроенный источник данных. Сразу отобразились данные:
К VictoriaMetrics TLS не прикручивал, ибо не нужно это публиковать. Также терзает сомнение, не помешает ли basic authentication подтверждению сертифката. Не разбирался еще.
Заключение
Очень понравилось, что я вообще не задумавался, что нода на ARM. Ни в helm чартах, ни в обычных yaml. Только kubectl ставил под разные платформы (Windows, Oracle Linux8 arm64, Ubuntu 20.04 amd64), дальше все само.
Было бы интересно добавить в этот кластер еще и x86 ноду. Но выбрать бесплатную E1.Micrо в визарде невозможно. А развернуть на отдельной машинке и потом kubeadm join
– вопрос, достойный разбирательства. Боюсь, Managed кластеру это не понравится.
Но какой-нибудь service mesh c домашним кубером со временем погоняю. Может, кстати, упростит решение давней проблемы – как публиковать «домашние» сервисы в Интернете (теперь через Ingress).