¿Cómo accedo a la API de Kubernetes desde un contenedor de pod?

118

Solía ​​poder rizarme

https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1beta3/namespaces/default/

como mi URL base, pero en kubernetes 0.18.0 me da "no autorizado". Lo extraño es que si usé la dirección IP externa de la máquina API ( http://172.17.8.101:8080/api/v1beta3/namespaces/default/), funciona bien.

tslater
fuente
¿Dónde está ejecutando su clúster (GCE, AWS, etc.) y qué sistema operativo base (debian, CoreOS, etc.)?
Robert Bailey
Vagrant / CoreOS ... eventualmente lo moveré a AWS / CoreOS
tslater
¿De dónde proceden las variables $KUBERNETES_SERVICE_HOSTy $KUBERNETES_PORT_443_TCP_PORT?
ruediste
Encontré esta guía increíble para 101 sobre cuentas de servicio, roles y enlaces de roles developer.ibm.com/recipes/tutorials/… . La última sección detalla cómo podemos acceder al formulario de la API k8 dentro de los pods.
viv

Respuestas:

132

En la documentación oficial encontré esto:

https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod

Aparentemente, me faltaba un token de seguridad que no necesitaba en una versión anterior de Kubernetes. A partir de eso, ideé lo que creo que es una solución más simple que ejecutar un proxy o instalar golang en mi contenedor. Vea este ejemplo que obtiene la información, de la api, para el contenedor actual:

KUBE_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -sSk -H "Authorization: Bearer $KUBE_TOKEN" \
      https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/default/pods/$HOSTNAME

También utilizo incluir un binario simple, jq ( http://stedolan.github.io/jq/download/ ), para analizar el json para usarlo en scripts bash.

tslater
fuente
5
Para los clústeres desplegadas recientemente es posible que desee cambiar v1beta3av1
Eyal Levin
6
Tenga en cuenta que este comando curl se conectará de forma insegura al apiserver (haciendo posible que un intermediario intercepte el token de portador), por lo que solo debe usarlo si la red entre el pod y el apiserver es de plena confianza. De lo contrario, debe pasar la --cacertbandera a curl para que curl valide el certificado presentado por el apiserver.
Robert Bailey
1
Tuve que usar KUBERNETES_SERVICE_HOST=kubernetes.default, $KUBERNETES_443_TCP_PORT=443, NAMESPACE == $ (</ var / run / secretos / kubernetes.io / ServiceAccount / espacio de nombres) . The URL was kubernetes.default: 443 / API / v1 / espacios de nombres / $ NAMESPACE / vainas / ... `. Tenga en cuenta que la versión de API se establece en v1 en lugar de v1beta3 y el espacio de nombres predeterminado se reemplazó con $ NAMESPACE.
ruediste
74

Cada pod tiene una cuenta de servicio aplicada automáticamente que le permite acceder al apiserver. La cuenta de servicio proporciona tanto las credenciales del cliente, en forma de token de portador, como el certificado de la autoridad de certificación que se utilizó para firmar el certificado presentado por el apiserver. Con estos dos datos, puede crear una conexión segura y autenticada con el apisever sin usar curl -k(también conocido como curl --insecure):

curl -v --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" https://kubernetes.default.svc/
Robert Bailey
fuente
2
Cabe señalar que para que el cacert y el token existan en la cuenta de servicio, se debe proporcionar un --root-ca-file=argumento al controlador de replicación cuando se inicie. (esto se maneja automáticamente en la mayoría de los instaladores de kubernetes). Consulte la discusión aquí para obtener más detalles: github.com/kubernetes/kubernetes/issues/10265
JKnight
7
Accedía al servidor API desde un pod con un espacio de nombres diferente. Por lo tanto, tuve que usar https://kubernetes.default/como anfitrión
ruediste
El anfitrión oficial está kubernetes.default.svcdocumentado en kubernetes.io/docs/tasks/access-application-cluster/…
Martin Tapp
17

Usando el cliente Python kubernetes ...

from kubernetes import client, config

config.load_incluster_config()
v1_core = client.CoreV1Api()
rix
fuente
1
¡Gracias! Aquí hay un pequeño repositorio con un ejemplo, basado en su respuesta, para que sea más sencillo jugar con este código.
Omer Levi Hevroni
10

versión wget:

KUBE_TOKEN=$(</var/run/secrets/kubernetes.io/serviceaccount/token)    
wget -vO- --ca-certificate /var/run/secrets/kubernetes.io/serviceaccount/ca.crt  --header "Authorization: Bearer $KUBE_TOKEN" https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/default/pods/$HOSTNAME
Halil Kaskavalci
fuente
6

El apéndice más importante a los detalles ya mencionados anteriormente es que el pod desde el que está intentando acceder al servidor API debe tener las capacidades RBAC para hacerlo.

Cada entidad en el sistema k8s se identifica mediante una cuenta de servicio (como la cuenta de usuario que se utiliza para los usuarios). Según las capacidades de RBAC, se completa el token de la cuenta de servicio (/var/run/secrets/kubernetes.io/serviceaccount/token). Los enlaces kube-api (por ejemplo, pykube) pueden tomar este token como entrada cuando se crea una conexión con los servidores kube-api. Si el pod tiene las capacidades de RBAC correctas, el pod podría establecer la conexión con el servidor kube-api.

pr-pal
fuente
5

Me encontré con este problema al intentar acceder a la API desde dentro de un pod usando Go Code. A continuación se muestra lo que implementé para que eso funcione, en caso de que alguien se encuentre con esta pregunta y quiera usar Go también.

El ejemplo usa un recurso de pod, para el cual debe usar la client-gobiblioteca si está trabajando con objetos nativos de Kubernetes. El código es más útil para quienes trabajan con CustomResourceDefintions.

serviceHost := os.GetEnv("KUBERNETES_SERVICE_HOST")
servicePort := os.GetEnv("KUBERNETES_SERVICE_PORT")
apiVersion := "v1" // For example
namespace := default // For example
resource := "pod" // For example
httpMethod := http.MethodGet // For Example

url := fmt.Sprintf("https://%s:%s/apis/%s/namespaces/%s/%s", serviceHost, servicePort, apiVersion, namespace, resource)

u, err := url.Parse(url)
if err != nil {
  panic(err)
}
req, err := http.NewRequest(httpMethod, u.String(), bytes.NewBuffer(payload))
if err != nil {
    return err
}

caToken, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
if err != nil {
    panic(err) // cannot find token file
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(caToken)))

caCertPool := x509.NewCertPool()
caCert, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
if err != nil {
    return panic(err) // Can't find cert file
}
caCertPool.AppendCertsFromPEM(caCert)

client := &http.Client{
  Transport: &http.Transport{
    TLSClientConfig: &tls.Config{
        RootCAs: caCertPool,
    },
  },
}

resp, err := client.Do(req)
if err != nil {
    log.Printf("sending helm deploy payload failed: %s", err.Error())
    return err
}
defer resp.Body.Close()

// Check resp.StatusCode
// Check resp.Status
KyleHodgetts
fuente
4

Desde el interior del pod, se puede acceder al servidor de la API de kubernetes directamente en " https: //kubernetes.default ". De forma predeterminada, utiliza la "cuenta de servicio predeterminada" para acceder al servidor api.

Por lo tanto, también necesitamos pasar un "certificado de ca" y un "token de cuenta de servicio predeterminado" para autenticarnos con el servidor api.

El archivo de certificado se almacena en la siguiente ubicación dentro del pod: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt

y el token de cuenta de servicio predeterminado en: /var/run/secrets/kubernetes.io/serviceaccount/token

Puede utilizar el cliente godaddy de nodejs kubbernetes .

let getRequestInfo = () => {
    return {
        url: "https://kubernetes.default",
        ca:   fs.readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt').toString(),
        auth: {
            bearer: fs.readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/token').toString(),
        },
        timeout: 1500
    };
}

let initK8objs = () =>{
    k8obj = getRequestInfo();
    k8score = new Api.Core(k8obj),
    k8s = new Api.Api(k8obj);
}

Utkarsh Yeolekar
fuente
3

Tuve un problema de autenticación similar en GKE donde los scripts de Python arrojaron excepciones repentinamente. La solución que funcionó para mí fue otorgar permiso a las vainas a través del rol

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: fabric8-rbac
subjects:
  - kind: ServiceAccount
  # Reference to upper's `metadata.name`
  name: default
  # Reference to upper's `metadata.namespace`
  namespace: default
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

para obtener más información, ingrese la descripción del enlace aquí

Pato de goma
fuente
2

Para quien esté usando Google Container Engine (con tecnología de Kubernetes):

Una simple llamada https://kubernetesdesde dentro del clúster usando este cliente de kubernetes para Java funciona.

Cahen
fuente
0
curl -v -cacert <path to>/ca.crt --cert <path to>/kubernetes-node.crt --key <path to>/kubernetes-node.key https://<ip:port>

Mi versión de k8s es 1.2.0, y en otras versiones se supone que también funciona ^ ^

Mañana Y
fuente
Lo anterior es correcto si tiene webhooks o algún otro RBAC habilitado. Esto es especialmente cierto> 1.2 de k8s
doktoroblivion
0

This is from the Kubernetes en acción book.

Debes encargarte de la autenticación . El servidor API en sí mismo dice que no está autorizado para acceder a él, porque no sabe quién es usted. .

Para autenticarse, necesita un token de autenticación. Afortunadamente, el token se proporciona a través del token secreto predeterminado mencionado anteriormente y se almacena en el archivo de token en el volumen secreto.

Vas a usar el token para acceder al servidor API . Primero, cargue el token en una variable de entorno:

root@myhome:/# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

El token ahora se almacena en la variable de entorno TOKEN . Puede usarlo al enviar solicitudes al servidor API:

root@curl:/# curl -H "Authorization: Bearer $TOKEN"  https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_PORT_443_TCP_PORT/api/v1/namespaces/default/pods/$HOSTNAME
   {  "paths": 
      [    
        "/api",    
        "/api/v1",   
        "/apis",    
        "/apis/apps",    
        "/apis/apps/v1beta1",    
        "/apis/authorization.k8s.io",        
         ...    
        "/ui/",    
        "/version"  
      ]
  }
fgul
fuente