Основы работы с kubernetes
Пререквизиты
Для выполнения практики необходимо иметь установленный docker.
Все приведенные примеры команд производятся в оболочке bash
, предполагая что имеется окружение
в виде GNU Coreutils. Если вы используете другие оболочки, то некоторые команды будут отличаться.
Для получения необходимой среды на Windows можно воспользоваться wsl или в независимости от платформы можно воспользоваться vagrant с Vagrantfile:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/lunar64"
config.vm.network "forwarded_port", guest: 6443, host: 6443
config.vm.provider "virtualbox" do |v|
v.memory = 4096
v.cpus = 4
end
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y docker.io
usermod -a -G docker vagrant
SHELL
end
kind
Для взаимодействия с кластером kubernetes нам в первую очередь необходим сам кластер. Для локальной работы и обучения предлагаю воспользоваться открытым проектом kind(Kubernetes in Docker), который позволяет запустить локальный кластер в Docker контейнере.
Установка
Варианты установки описаны на сайте kind.sigs.k8s.io, можно воспользоваться
пакетным менеджером или достаточно просто скачать и положить в каталог из переменной PATH
исполняемый файл для своей ОС и архитектуры со страницы releases.
Вариант для Linux:
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
Создание кластера
Для создания кластера достаточно запустить команду:
kind create cluster
Посмотреть созданные кластера можно командой:
kind get clusters
kubectl
Для взаимодействия с кластером нам понадобится утилита kubectl - это инструмент командной строки для управления кластерами Kubernetes.
Установка
Варианты установки описаны на сайте kubernetes для каждой ОС. Здесь также достаточно
скачать исполняемый файл и положить в каталог из переменной PATH
.
Вариант для Linux:
curl -Lo ./kubectl \
"https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl
Конфигурация
По умолчанию конфигурация находится в домашней директории по пути ~/.kube/config
, переопределить
путь конфигурационного файла можно либо через переменную среды KUBECONFIG
, либо явно указывая
опцию --kubeconfig
. Сам файл конфигурации можно посмотреть командой kubectl config view
.
kubectl config view --kubeconfig=~/.kube/config1
export KUBECONFIG=~/.kube/config2
kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://127.0.0.1:6443
name: kind-kind
contexts:
- context:
cluster: kind-kind
namespace: default
user: kind-kind
name: kind-kind
current-context: kind-kind
kind: Config
preferences: {}
users:
- name: kind-kind
user:
client-certificate-data: DATA+OMITTED
client-key-data: DATA+OMITTED
В самой конфигурации можно выделить основные параметры:
clusters - список кластеров, где указаны параметры подключения
users - список учетных данных для подключения
contexts - список именованных объединений конкретного кластера с учетными данными для подключения
current-context - текущий выбранный контекст с которым и будет работать утилита kubectl
Автодополнение
Для удобного использования утилиты kubectl
можно воспользоваться функцией автоматического дополнения
вводимой команды. На текущий момент это работает для командных оболочек bash
, zsh
, fish
и
powershell
. Для подробной информации можно выполнить команду kubectl completion -h
.
Вариант для bash:
# для текущей сессии bash, должен быть установлен пакет bash-completion
source <(kubectl completion bash)
# для запуска при инициализации bash
echo "source <(kubectl completion bash)" >> ~/.bashrc
Также для удобного использования можно создать псевдоним для kubectl
в виде команды k
:
# для текущей сессии bash
alias k=kubectl
complete -F __start_kubectl k
# для запуска при инициализации bash
echo -e "alias k=kubectl\ncomplete -F __start_kubectl k" >> ~/.bashrc
Информация о ресурсах
Получить список всех типов ресурсов в кластере можно командой kubectl api-resources
:
$ k api-resources | head
NAME SHORTNAMES APIVERSION NAMESPACED KIND
bindings v1 true Binding
componentstatuses cs v1 false ComponentStatus
configmaps cm v1 true ConfigMap
endpoints ep v1 true Endpoints
events ev v1 true Event
limitranges limits v1 true LimitRange
namespaces ns v1 false Namespace
nodes no v1 false Node
persistentvolumeclaims pvc v1 true PersistentVolumeClaim
Здесь можно увидеть следующие столбцы:
NAME - имя ресурса
SHORTNAMES - короткие имена, которые можно использовать при работе с этим ресурсом
APIVERSION - поле apiVersion, которое используется в объектах данного типа ресурса
NAMESPACED - является ли данный ресурс namespaced или cluster-wide
KIND - поле kind, которое используется в объектах данного типа ресурса
Для получения информации о ресурсе можно воспользоваться командой kubectl explain
:
$ k explain pod
KIND: Pod
VERSION: v1
DESCRIPTION:
Pod is a collection of containers that can run on a host. This resource is
created by clients and scheduled onto hosts.
FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
spec <Object>
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
status <Object>
Most recently observed status of the pod. This data may not be up to date.
Populated by the system. Read-only. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
Тут представлено описание DESCRIPTION
, а также в FIELDS
все известные api поля и их тип.
Можно обратиться к конкретному полю за дополнительной вложенной информацией:
$ k explain pod.spec | head -20
KIND: Pod
VERSION: v1
RESOURCE: spec <Object>
DESCRIPTION:
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
PodSpec is a description of a pod.
FIELDS:
activeDeadlineSeconds <integer>
Optional duration in seconds the pod may be active on the node relative to
StartTime before the system will actively try to mark it failed and kill
associated containers. Value must be a positive integer.
affinity <Object>
If specified, the pod's scheduling constraints
$ k explain pod.spec.containers.command
KIND: Pod
VERSION: v1
FIELD: command <[]string>
DESCRIPTION:
Entrypoint array. Not executed within a shell. The container image's
ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME)
are expanded using the container's environment. If a variable cannot be
resolved, the reference in the input string will be unchanged. Double $$
are reduced to a single $, which allows for escaping the $(VAR_NAME)
syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)".
Escaped references will never be expanded, regardless of whether the
variable exists or not. Cannot be updated. More info:
https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
Работа с объектами
Утилита kubectl
позволяет производить CRUD операции над объектами в kubernetes. Для этого
можно использовать команды create
, get
, replace
, patch
, delete
- по их названию можно
понять какие операции они производят. Отдельно хочется выделить команды apply
, diff
,
describe
и edit
:
apply - создает или обновляет существующий объект, сохраняя примененную конфигурацию в аннотацию
kubectl.kubernetes.io/last-applied-configuration
для расчета необходимых изменений при последующих обновленияхdiff - показывает изменения, которые произойдут в кластере при применении новой конфигурации объекта
describe - выдает состояние объекта в удобочитаемом формате
edit - открывает ресурс для редактирования в текстовом редакторе, определенном в переменной среды
KUBE_EDITOR
илиEDITOR
Также есть ряд специализированных команд, которые позволяют выполнять конкретные действия в кластере:
run
, logs
, set
, label
, exec
, debug
, scale
и многие другие. Их мы рассмотрим позже.
Полезные функции
Прежде чем начать работать с объектами хочется затронуть некоторые полезные функции в утилите kubectl
.
Через флаг -v<N>
можно задать уровень логирования утилиты, где N
- необходимый уровень. Это полезно
для отладки или для обучения, так как позволит понять какой именно REST запрос отправляется на сервер.
Отмечу три полезных уровня:
-v6
- отображается HTTP запрос отправляемый в api-v8
- помимо запроса также отображается и тело ответа от api-v10
- максимальный уровень, отображает также и пример данного запроса с использованием утилитыcurl
для возможности сформировать запрос в ручном режиме
Через флаг -o
или --output
можно управлять форматом вывода.
-o name
выводит только имена объектов-o wide
расширенный вывод в табличном виде-o yaml
выводит содержимое вyaml
формате-o json
выводит содержимое вjson
формате-o jsonpath=
выводит поля заданные выражением jsonpath-o custom-columns=
выводит содержимое в табличном виде с заданными столбцами
Через флаг --dry-run
можно производить пробный прогон без реальных изменений в кластере.
Есть два варианта:
--dry-run=client
- клиент сам формирует конфигурацию объекта без отправки его в api--dry-run=server
- клиент формирует конфигурацию объекта с отправкой в api, а api производит операции валидации и мутации объекта, но не сохраняет его в базе
Работа c объектами на примере kind: Pod
Для создания объекта пода есть специальная команда run
-
kubectl run --image=<docker_image> <name>
:
$ k run --image nginx nginx --dry-run=client -o yaml # для начала посмотрим содержимое без применения
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
$ k run --image nginx nginx
pod/nginx created
Для получения информации о созданном поде можно воспользоваться командами get
и describe
:
$ k get po
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 80s
$ k describe po nginx
Name: nginx
Namespace: default
Priority: 0
Service Account: default
Node: kind-control-plane/172.18.0.2
Start Time: Sun, 19 Feb 2023 16:54:24 +0300
Labels: run=nginx
Annotations: <none>
Status: Running
IP: 10.244.0.6
IPs:
IP: 10.244.0.6
Containers:
nginx:
Container ID: containerd://5b640dd7561025918ada550c1c513cf60392acc443a5c27ee64504efa0024cba
Image: nginx
Image ID: docker.io/library/nginx@sha256:6650513efd1d27c1f8a5351cbd33edf85cc7e0d9d0fcb4ffb23d8fa89b601ba8
Port: <none>
Host Port: <none>
State: Running
Started: Sun, 19 Feb 2023 16:54:37 +0300
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-dzptg (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-dzptg:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m18s default-scheduler Successfully assigned default/nginx to kind-control-plane
Normal Pulling 2m17s kubelet Pulling image "nginx"
Normal Pulled 2m5s kubelet Successfully pulled image "nginx" in 11.713549374s
Normal Created 2m5s kubelet Created container nginx
Normal Started 2m5s kubelet Started container nginx
Выполнить команду внутри контейнера можно командой kubectl exec <pod> -- <command>
:
$ kubectl exec nginx -- cat /proc/1/cmdline
nginx: master process nginx -g daemon off;
Посмотреть лог можно командой kubectl logs <pod>
, если в поде несколько контейнеров, то указать
конкретный можно через ключ -c
- kubectl logs <pod> -c <container>
:
$ k logs nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2023/02/19 13:54:37 [notice] 1#1: using the "epoll" event method
2023/02/19 13:54:37 [notice] 1#1: nginx/1.23.3
2023/02/19 13:54:37 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/02/19 13:54:37 [notice] 1#1: OS: Linux 4.19.130-boot2docker
2023/02/19 13:54:37 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2023/02/19 13:54:37 [notice] 1#1: start worker processes
2023/02/19 13:54:37 [notice] 1#1: start worker process 36
2023/02/19 13:54:37 [notice] 1#1: start worker process 37
2023/02/19 13:54:37 [notice] 1#1: start worker process 38
2023/02/19 13:54:37 [notice] 1#1: start worker process 39
Если процесс внутри контейнера завершился и перезапустился, то лог предыдущей работы можно посмотреть
с помощью флага --previous
:
$ k exec nginx -- /bin/sh -c 'kill 1'
$ k get po nginx
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 2 (22s ago) 83m
$ k logs nginx --previous
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2023/02/19 15:11:03 [notice] 1#1: using the "epoll" event method
2023/02/19 15:11:03 [notice] 1#1: nginx/1.23.3
2023/02/19 15:11:03 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/02/19 15:11:03 [notice] 1#1: OS: Linux 4.19.130-boot2docker
2023/02/19 15:11:03 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2023/02/19 15:11:03 [notice] 1#1: start worker processes
2023/02/19 15:11:03 [notice] 1#1: start worker process 35
2023/02/19 15:11:03 [notice] 1#1: start worker process 36
2023/02/19 15:11:03 [notice] 1#1: start worker process 37
2023/02/19 15:11:03 [notice] 1#1: start worker process 38
2023/02/19 15:17:19 [notice] 1#1: signal 15 (SIGTERM) received from 82, exiting
2023/02/19 15:17:19 [notice] 35#35: exiting
2023/02/19 15:17:19 [notice] 35#35: exit
2023/02/19 15:17:19 [notice] 36#36: exiting
2023/02/19 15:17:19 [notice] 36#36: exit
2023/02/19 15:17:19 [notice] 37#37: exiting
2023/02/19 15:17:19 [notice] 37#37: exit
2023/02/19 15:17:19 [notice] 38#38: exiting
2023/02/19 15:17:19 [notice] 38#38: exit
2023/02/19 15:17:19 [notice] 1#1: signal 17 (SIGCHLD) received from 36
2023/02/19 15:17:19 [notice] 1#1: worker process 35 exited with code 0
2023/02/19 15:17:19 [notice] 1#1: worker process 36 exited with code 0
2023/02/19 15:17:19 [notice] 1#1: signal 29 (SIGIO) received
2023/02/19 15:17:19 [notice] 1#1: signal 17 (SIGCHLD) received from 37
2023/02/19 15:17:19 [notice] 1#1: worker process 37 exited with code 0
2023/02/19 15:17:19 [notice] 1#1: worker process 38 exited with code 0
2023/02/19 15:17:19 [notice] 1#1: exit
На объекты можно навешивать метки, чтобы потом было удобно фильтровать объекты по определенным меткам.
Для этого можно либо изменить непосредственно в самом объекте секцию metadata.labels
,
либо воспользоваться командой kubectl label
:
$ k label pod nginx app=old
pod/nginx labeled
Давайте добавим еще пару подов и пролейблим их:
$ k run --image nginx nginx1
pod/nginx1 created
$ k run --image nginx nginx2
pod/nginx2 created
pod/nginx labeled
$ k label pod nginx1 app=new
pod/nginx1 labeled
$ k label pod nginx2 app=new
pod/nginx2 labeled
Посмотреть все лейблы на объектах можно добавив к команде kubectl get
опцию --show-labels
:
$ k get po --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx 1/1 Running 2 4h13m app=old,run=nginx
nginx1 1/1 Running 0 3h20m app=new,run=nginx1
nginx2 1/1 Running 0 3h20m app=new,run=nginx2
Теперь при получении объектов мы можем использовать селектор с помощью ключа -l
:
$ k get po -l app=old
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 59m
$ k get po -l app=new
NAME READY STATUS RESTARTS AGE
nginx1 1/1 Running 0 6m44s
nginx2 1/1 Running 0 6m42s
Через запятую можно указывать несколько лейблов -l key1=value1,key2=value2
, объект должен
удовлетворять сразу всем условиям:
$ k get po -l run=nginx1,app=new
NAME READY STATUS RESTARTS AGE
nginx1 1/1 Running 0 11m
Операция просмотра логов также может выполняться с селектором, для добавления информации из какого пода
конкретная строка лога можно с помощью опции --prefix
:
$ k logs -l app=new --prefix
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: using the "epoll" event method
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: nginx/1.23.3
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: OS: Linux 4.19.130-boot2docker
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: start worker processes
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: start worker process 36
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: start worker process 37
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: start worker process 38
[pod/nginx1/nginx1] 2023/02/19 14:47:46 [notice] 1#1: start worker process 39
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: using the "epoll" event method
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: nginx/1.23.3
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: OS: Linux 4.19.130-boot2docker
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: start worker processes
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: start worker process 35
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: start worker process 36
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: start worker process 37
[pod/nginx2/nginx2] 2023/02/19 14:47:49 [notice] 1#1: start worker process 38
Аннотации можно добавлять с помощью команды kubectl annotate
также как и лейблы:
$ k annotate pod nginx key=value
pod/nginx annotated
Для изменения пода можно воспользоваться командой kubectl edit
, которая откроет его в текстовом
редакторе определенном в переменной среды KUBE_EDITOR
:
$ export KUBE_EDITOR=nano
$ k edit po nginx
UW PICO 5.09 File: /var/folders/62/_p4ngvx105qdskglcfwb2j8hr9_rkn/T/kubectl-edit-4203624632.yaml Modified
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Pod
metadata:
annotations:
key: value1
creationTimestamp: "2023-02-19T13:54:24Z"
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
Изменим значение аннотации и сохраним Ctrl-X
:
pod/nginx edited
Другой способ редактирования объектов возможен через сохранение текущего состояния в файл, редактирование этого файла и последующее применение в кластере:
Сперва мы получаем под командой
kubectl get
и сохраняем в текущей директории$ k get po nginx -o yaml > nginx.yaml
Редактируем данный файл удобным средствами
$ sed -i 's/value1/value2/' nginx.yaml
Можем посмотреть разницу значений в файле с значениями в кластере командой
kubectl diff
$ k diff -f nginx.yaml diff -u -N /var/folders/62/_p4ngvx105qdskglcfwb2j8hr9_rkn/T/LIVE-3347262426/v1.Pod.default.nginx /var/folders/62/_p4ngvx105qdskglcfwb2j8hr9_rkn/T/MERGED-1669273324/v1.Pod.default.nginx --- /var/folders/62/_p4ngvx105qdskglcfwb2j8hr9_rkn/T/LIVE-3347262426/v1.Pod.default.nginx 2023-02-19 20:25:10.000000000 +0300 +++ /var/folders/62/_p4ngvx105qdskglcfwb2j8hr9_rkn/T/MERGED-1669273324/v1.Pod.default.nginx 2023-02-19 20:25:10.000000000 +0300 @@ -2,7 +2,7 @@ kind: Pod metadata: annotations: - key: value1 + key: value2 creationTimestamp: "2023-02-19T13:54:24Z" labels: app: old
Применяем значения из файла в кластере командой
kubectl apply
$ k apply -f nginx.yaml Warning: resource pods/nginx is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically. pod/nginx configured