[Перевод] В Kubernetes 1.18 сломали «kubectl run». Что на замену?

Если вы применяли в работе предыдущие выпуски Kubernetes — тот же 1.16 — вы, возможно, видели странное предупреждение от kubectl run.

До недавнего времени этой командой можно было сгенерировать объекты для развертывания или YAML из командной строки. Начиная с Kubernetes 1.18 такая возможность была сломана весьма удивительным образом. В статье будут ответы на вопросы «Почему?» и «Как дальше с этим жить?»

2vin8vg-vktg4arytrpw68jv6ge.png

Прежде чем занырнуть поглубже в эти вопросы, вспомним, что есть три основных способа создания объектов для развертывания в Kubernetes:


  • Через API (как это делает например OpenFaaS)
  • Путем создания вручную файла YAML. Или более приближенно к реальность — копипасты в него откуда-то из разных сайтов (привет, StackOverflow)
  • Запуская kubectl run или kubectl run -o yaml --dry-run

Как видно — я также перечислил варианты по порядку по сложности (от сложного до простого).


Сравнение вариантов

Первый случай, использование API — возможно наиболее стабильный путь для развертывания, но и наиболее сложный в реализации. До недавнего времени API был относительно стабильным в «extensions/v1beta», пока не перешел в «apps/v1», это было достаточно большое изменение и многие проекты с открытым исходным кодом должны были переработать свой код и файлы YAML. Я не уверен, что сложнее — создавать объекты в Go или YAML. С Go вы получаете возможность подсказок IDE, но нужно искать описание пакетов, где и в каком из них требуемый тип. Core? Meta? Apps? Extensions? Может быть это то, что мы упускаем при редактировании манифестов — старый добрый Intellisense?

g8bqjtlj9qyc55tpald7irwrdmo.png
Кусочек кода из контроллера OpenFaaS для Kubernetes (aka faas-netes)

Второй вариант — создание YAML вручную. Если пользовались Twitter или смотрели по хэштегу #kubernetes, то могли видеть еще и то, что все, от новичков, уважаемых авторов и до основателей Kubernetes, ругаются на то, как трудно писать и понимать YAML.

Лично мне он нравится, и я считаю, что его легче всего применять, но я то понимаю, что к чему с жалобами. Я предполагаю, что они на самом деле жалуются на сложную и вложенную структуру схемы API Kubernetes.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-1
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

Этот пример скопирован из документации к Kubernetes, а затем число реплик изменено с трех на одну. Лично мне не хотелось бы писать такое с нуля, хоть убейте.


Написание Kubernetes YAML напоминает мне задачу на собеседовании на листике с карандашом про переворачивание двойного связного списка, без ошибок и опечаток (сложно, но можно прим. переводчика).

Третий вариант до недавнего времени был весьма приятной альтернативой первому и второму вариантам:


  • особенно для обучения;
  • при необходимости написания коротких инструкций для демонстрации приложения;
  • для создания YAML для дальнейшей настройки;
  • использования в конвейерах CI\CD.

Например, для раскатки приложения в кластере с политикой перезапуска, именем, образом и портом можно сделать так:

$ kubectl run nginx-1 --image=nginx --port=80 --restart=Always

Если хотите лучше, можно смешать вариант два и три — выводим YAML без его применения, после чего ковыряем его. Давайте сравним то, что выводится командой и документацией?

$ kubectl run nginx-1 --image=nginx:1.14.2 --port=80 \
    --restart=Always -o yaml --dry-run
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: nginx-1
  name: nginx-1
spec:
  replicas: 1
  selector:
    matchLabels:
      run: nginx-1
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: nginx-1
    spec:
      containers:
      - image: nginx:1.14.2
        name: nginx-1
        ports:
        - containerPort: 80
        resources: {}

Еще есть пара флагов, типа --replicas и --serviceaccount. Вывод не сильно отличается от документации.

Но причина написания этой статьи — вы больше не сможете так делать, поскольку эта функция удалена, начиная с Kubernetes версии 1.18 и более поздних выпусков.


Предупреждения

Я всегда был немного удивлён этой ошибкой, но в ней не было ни времени, ни другого контекста для помощи.


Как и вы, я подумал: «Ну, допустим, в будущем я выучу альтернативную команду»
$ kubectl run nginx-1 --image=nginx --port=80 --restart=Always
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx-1 created

Если вы скачаете kubectl 1.17 или более раннюю (я работаю 1.15 в этой статье), то вы все еще сможете разворачивать приложения или создавать YAML. Но как только вы обновитесь до Kubernetes 1.18 получится так:

kubectl run nginx-1 --image=nginx --port=80 --restart=Always -o yaml --dry-run
W0512 14:27:13.111424   30104 helpers.go:535] --dry-run is deprecated and can be replaced with --dry-run=client.
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx-1
  name: nginx-1
spec:
  containers:
  - image: nginx
    name: nginx-1
    ports:
    - containerPort: 80
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always

Это Pod — не то же самое что Deployment. К счастью все еще можно указать service account и порт, но работать напрямую с подами не стоит, особенно на боевых серверах, поскольку они эфемерны, могут переехать на другой узел, быть убиты и не имеют (по умолчанию) стабильного имени или ip-адреса. Deployments и Services решают эту проблему.

Работать с подами можно, для одноразовых задач типа запуска curl для проверки api, или же для добавления сообщений в Kafka.

Мы с этим столкнулись в проекте inlets-operator, когда пользователь пожаловался, что примеры из документации не работают.

rbgo70edaifitxdlbqpmskvmwcu.png

Мы хотели простейшую инструкцию для документации и для вспомогательного сообщения helm chart, которая помогла бы пользователям понять, как пользоваться проектом. Вы создаете Deployment, затем открываете доступ извне через LoadBalancer, мы создаем VM в вашем любимом облаке, создаем обратный туннель, так что вы в результате имеете полностью внедренный белый ip-адрес.

Наша старая документация выглядела так:

kubectl run nginx-1 --image=nginx --port=80 --restart=Always
kubectl expose deployment nginx-1 --port=80 --type=LoadBalancer

Новая так:

export DEPLOYMENT=nginx-1
(cat<


Просьба к разработчикам Kubernetes, пожалуйста не отбирайте у нас команду kubectl expose;-)

Жду с нетерпением сохранения текстов такого же объема во всех моих руководствах, а также начинаю привыкать писать простыни для Deployment. Я был настолько недоволен полученным руководством, что поместил его в отдельный файл, который надо будет скачать всем желающим для проверки перед запуском кластера:

$ kubectl apply -f https://raw.githubusercontent.com/inlets/inlets-operator/master/contrib/nginx-sample-deployment.yaml

Люди, никогда не делайте kubectl apply -f файлов из интернета без их предварительной проверки.


Что делать?

Оказывается, нет никакой полноценной альтернативы. Некоторые считают, что новая команда это kubectl create deployment.


Самые внимательные уже заметили, что все важные опции отсутствуют, к примеру --port, --serviceaccount, --replicas и --restart-policy.
kubectl create deployment --help
Create a deployment with the specified name.
Aliases:
deployment, deploy
Examples:
  # Create a new deployment named my-dep that runs the busybox image.
  kubectl create deployment my-dep --image=busybox
Options:
      --allow-missing-template-keys=true: If true, ignore any errors in templates when a field or map key is missing in
the template. Only applies to golang and jsonpath output formats.
      --dry-run='none': Must be "none", "server", or "client". If client strategy, only print the object that would be
sent, without sending it. If server strategy, submit server-side request without persisting the resource.
      --image=[]: Image name to run.
  -o, --output='': Output format. One of:
json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file.
      --save-config=false: If true, the configuration of current object will be saved in its annotation. Otherwise, the
annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.
      --template='': Template string or path to template file to use when -o=go-template, -o=go-template-file. The
template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
      --validate=true: If true, use a schema to validate the input before sending it
Usage:
  kubectl create deployment NAME --image=image [--dry-run=server|client|none] [options]
Use "kubectl options" for a list of global command-line options (applies to all commands).

Так что наш изначальный пример не будет больше работать с kubectl create, а также не может быть применен иначе, как блок YAML. Если нужен динамический порт — есть вариант с шаблонами на bash, что однако не кроссплатформенно.

Лучшее что можно сделать с новой командой — это очередной поиск по StackOverflow или документации Kubernetes каждый раз для простого Deployment. Почему? Потому что вложенные схемы на YAML сложно запоминать.

$ kubectl create deployment nginx-1 --image=nginx  -o yaml --dry-run
W0512 14:38:18.296270   30135 helpers.go:535] --dry-run is deprecated and can be replaced with --dry-run=client.
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx-1
  name: nginx-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-1
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx-1
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}


Обратите внимание, что нет порта, restartPolicy и других вещей.


Какие выводы?

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

Pull request 68132 внес все это, желая приблизить поведение kubectl run к docker run. Вообще неплохая задумка.

Я надеялся, что разработчики Kubernetes рассмотрят вопрос о добавлении различных отсутствующих флагов и опций в новую команду kubectl create deployment, однако первому же человеку, пожелавшему такое, сказали: «хочешь — пиши свой PR».

1hw3qgxeoxblznrjwvhma1im9r8.png

Вас это тоже затронуло? Пишите в Twitter, или комментируйте запрос, в котором будут изменения.

Трудно сделать что-то лучше, особенно если это ломает пользовательский опыт, однако это у вас, авторов Kubernetes, «получилось». И вышло просто «идеально».

Полный список изменений в 1.18.

Статья переведена и подготовлена для Хабра сотрудниками обучающего центра Слёрм — интенсивы, видеокурсы и корпоративное обучение от практикующих специалистов (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

© Habrahabr.ru