Многие знают про аттракцион необычайной щедрости от 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/rootSize 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 в виде беты. sudo -iapt install wireguardcd /etc/wireguardumask 077 wg genkey | tee privatekey | wg pubkey > publickey cat <<EOF > wg0.confAddress = 192.168.16.4/24SaveConfig = truePostUp = 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 MASQUERADEListenPort = 51820PrivateKey = XXX=PublicKey = YYY=AllowedIPs = 192.168.16.1/32, 192.168.4.0/22EOF Разрешить форвардинг пакетов между интерфейсами echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.confsysctl -p Важно: нужно поправить настройку VNIC у этой виртуальной машины. И там Skip Source/Destination Check Раньше нужно было перезагрузиться после установки. Сейчас вроде не обязательно, но не повредит. wg-quick up wg0dmesg -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.servicesystemctl daemon-reloadsystemctl start wg-quick@wg0 И нужно входящие UDP пакеты разрешить. И сохранить  правила iptables. sudo iptables -I INPUT 1 -i ens3 -p udp --dport 51820 -j ACCEPTsudo /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. Ниже я покажу упрощенный вариант, но на этой виртуальной Ubuntu я делал все стандартно sudo apt install python3-pipsudo 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 можно стандартно через 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 kubectlsudo install -o root -g root -m 0755 kubectl /usr/bin/kubectlsource <(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, не считаю это существенным снижением безопасности. Как из 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 Подготовку закончили, пора уже что-то полезное развернуть. Я, конечно, начал с Nginx Ingress Controller. При этом автоматически создался Network Load Balancer. Кстати, при большом желании на нем можно прокинуть TCP/6443 на kube-api и TCP/22 на ноду. Но это не спортивно.  Если захочется поработать непосредственно с ноды, можно helm и для ARM поставить https://github.com/helm/helm/releaseswget https://get.helm.sh/helm-v3.8.0-linux-arm64.tar.gzsudo install -o root -g root -m 0755 kubectl /usr/bin/kubectlsudo install -o root -g root -m 0755 linux-arm64/helm /usr/bin/helmrm -rf linux-arm64 Но я уже все дальше с домашней VM делал helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update helm search repo ingressNAME                            CHART VERSION   APP VERSION     DESCRIPTIONingress-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, без него будут ругательства. Без него даже 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 Значительно быстрее 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/v1kind: StorageClassmetadata:  name: localprovisioner: kubernetes.io/no-provisionervolumeBindingMode: WaitForFirstConsumer--- apiVersion: v1kind: PersistentVolumemetadata:  name: server-volume-vicmet-victoria-metrics-single-server-0  labels:    type: localspec:  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 updatehelm 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: victoriametricsEOF Настройки скрапинга хранятся в 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/v1kind: Ingressmetadata:  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: ImplementationSpecificEOF После обновления DNS (CNAME на LoadBalancer IP, кучи IP не надо, бесплатно только 6 дают) http://vicmet.antn.in спрашивает пароль и потом позволяет посмотреть таргеты, метрики… Еще немного осталось :) Grafana я установлю не через helm, а просто подготовленным yaml. sudo mkdir -p /mnt/LoSt/grafanasudo chown 472 /mnt/LoSt/grafana Без этого grafana не сможет писать в директорию и не запустится. cat <<EOF | kubectl apply -f -apiVersion: v1kind: PersistentVolumemetadata:  name: grafana-pv  labels:    type: localspec:  storageClassName: "local"  capacity:    storage: 1Gi  accessModes:    - ReadWriteOnce  hostPath:    path: "/mnt/LoSt/grafana"--- apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: grafana-pv  namespace: sysmon  labels:    app: grafanaspec:  storageClassName: "local"  accessModes:    - ReadWriteOnce  resources:    requests:      storage: 1Gi--- apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: grafana  name: grafana  namespace: sysmonspec:  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: v1kind: Servicemetadata:  name: grafana  namespace: sysmonspec:  ports:    - port: 3000      protocol: TCP      targetPort: http-grafana  selector:    app: grafana  sessionAffinity: None  type: ClusterIP--- apiVersion: networking.k8s.io/v1kind: Ingressmetadata:  name: grafana  namespace: sysmonspec:  ingressClassName: nginx  rules:  - host: grafana.antn.in    http:      paths:      - backend:          service:            name: grafana            port:              number: 3000        path: /        pathType: ImplementationSpecificEOF И финальный аккорд – настроим 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/v1kind: ClusterIssuermetadata:  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/v1kind: ClusterIssuermetadata:  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: nginxEOF Сначала проверил на staging, потом переключился на prod. Из-за кеширования в браузере удобнее проверять в incognito mode. Еще можен понадобиться удалить прежний сертифкат (kubectl delete secret -n sysmon grafana-tls) Соответственно обновленный ingress, теперь с сертификатом. Просто помимо секции tls добавили аннотацию, «призывающую» cert-manager.  cat <<EOF | kubectl apply -f -apiVersion: networking.k8s.io/v1kind: Ingressmetadata:  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: ImplementationSpecificEOF Все подключилось. Добавил источник данных (тип 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).