AWS: eksctl — «Put http://169.254.169.254/latest/api/token: net/http: request canceled»

Автор: | 04/16/2020
 

Имеется Docker-образ с eksctl.

Имеется ЕС2 с Linux, на которой установлен eksctl.

К ЕС2 поключен AWS IAM Instance Profile с политикой AdminAccess.

На ЕС2 работает Jenkins, в Docker-контейнере, и свои джобы он запускает в отдельных контейнерах.

Среди прочих — есть джоба на создание Elastic Kubernetes Service, которая вызывается из контейнера с eksctl.

Проблема: при запуске напрямую с хоста — аутентификация проходит, ресурсы доступны:

eksctl --region us-east-2 get cluster
NAME                            REGION
bttrm-eks-dev-0                 us-east-2
bttrm-eks-prod-0                us-east-2
eksctl-bttrm-eks-production-1   us-east-2

Put http://169.254.169.254/latest/api/token: net/http: request canceled (Client.Timeout exceeded while awaiting headers)) from ec2metadata/GetToken

А при запуске из Docker-контейнера — возникает ошибка «Put http://169.254.169.254/latest/api/token: net/http: request canceled (Client.Timeout exceeded while awaiting headers)) from ec2metadata/GetToken«:

admin@jenkins-production:~$ docker run bttrm/kubectl-aws:2.5 eksctl get cluster
[!]  retryable error (RequestError: send request failed
caused by: Put http://169.254.169.254/latest/api/token: net/http: request canceled (Client.Timeout exceeded while awaiting headers)) from ec2metadata/GetToken - will retry after delay of 46.10367ms

Но почему?

И что вообще за ошибка?

AWS Config и аутентификация

Второй интересный момент, который заметил, и который помог двигаться в нужном направлении — это файл настроек AWS CLI: если прокинуть его в Docker, и в нём будут какие-то актуальные данные доступа — то всё работает, как ожидается.

Т.е. проблема возникает только тогда, когда eksctl не может аутентифироваться через ACCESS и SECRET ключи.

Файл конфига:

admin@jenkins-production:~$ cat .aws/config
[default]
output = json
region = us-east-2

Секретов:

admin@jenkins-production:~$ cat .aws/credentials
[default]
aws_access_key_id = AKI***D4Q
aws_secret_access_key = QUC***BTI

Пробуем с пробросом конфига:

admin@jenkins-production:~$ docker run -v /home/admin/.aws:/root/.aws/ bttrm/kubectl-aws:2.5 eksctl get cluster
NAME                            REGION
bttrm-eks-dev-0                 us-east-2
bttrm-eks-prod-0                us-east-2
eksctl-bttrm-eks-production-1   us-east-2

И без него:

admin@jenkins-production:~$ docker run bttrm/kubectl-aws:2.5 eksctl get cluster
[!]  retryable error (RequestError: send request failed
caused by: Put http://169.254.169.254/latest/api/token: net/http: request canceled (Client.Timeout exceeded while awaiting headers)) from ec2metadata/GetToken - will retry after delay of 32.822673ms

AWS EC2 Instance Metadata

Первое, что бросается в глаза — это адрес 169.254.169.254.

Почему? Потому что явно помним, что вроде такой же адрес использовался для получения метаданных инстанса, например — он использовался в AWS: ротация ключей IAM пользователей, EC2 IAM Roles и Jenkins.

Глянем дальше, на URI — latest/api/token — тут явно идёт запрос на получение токена аутентификации через Instance Role хоста — логично, так и должно быть: раз eksctl не смог получить данные из переменных окружения и файла настроек — он пытается получить их через Instance Profile.

Но почему PUT? Ведь раньше было, кажется, через GET? Да и логичнее GET звучит.

После недолгого гугления выяснилось, что AWS обновил процесс аутентификации при получении метаданных с ЕС2, см. Configuring the instance metadata service и ссылки в конце поста.

Пробуем получить токен с хоста напрямую:

admin@jenkins-production:~$ curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"
AQA***Cyg==

И из контейнера:

admin@jenkins-production:~$ docker run bttrm/kubectl-aws:2.5 curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"
...
curl: (56) Recv failure: Connection reset by peer

И ничего.

И какого?

А что мы можем сделать?

А мы можем просто пробросить сеть с хоста в контейнер, а не использовать сеть самого Docker:

admin@jenkins-production:~$ docker run --net=host bttrm/kubectl-aws:2.5 curl -sX PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"
AQA***ZmA==

Готово.

Ссылки по теме