Jak uzyskać dostęp do interfejsu API Kubernetes z poziomu kontenera pod?

118

Kiedyś potrafiłem się zwijać

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

jako mój podstawowy adres URL, ale w kubernetes 0.18.0 daje mi to „nieautoryzowane”. Dziwne jest to, że jeśli użyłem zewnętrznego adresu IP maszyny API ( http://172.17.8.101:8080/api/v1beta3/namespaces/default/), to działa dobrze.

tslater
źródło
Gdzie używasz swojego klastra (GCE, AWS itp.) I jakiego podstawowego systemu operacyjnego (debian, CoreOS itp.)?
Robert Bailey,
Vagrant / CoreOS ... ostatecznie przeniosę to na AWS / CoreOS
tslater
Skąd się biorą zmienne $KUBERNETES_SERVICE_HOSTi $KUBERNETES_PORT_443_TCP_PORT?
ruediste
Uważam, że ten przewodnik jest niesamowity dla 101 na kontach usług, rolach i rolebindings developer.ibm.com/recipes/tutorials/… . Ostatnia sekcja szczegółowo opisuje, w jaki sposób możemy uzyskać dostęp do formularza API k8 w podach.
viv

Odpowiedzi:

132

W oficjalnej dokumentacji znalazłem to:

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

Najwyraźniej brakowało mi tokena bezpieczeństwa, którego nie potrzebowałem w poprzedniej wersji Kubernetes. Na tej podstawie wymyśliłem coś, co moim zdaniem jest prostsze niż uruchomienie serwera proxy lub zainstalowanie golanga na moim kontenerze. Zobacz ten przykład, który pobiera informacje z interfejsu API dla bieżącego kontenera:

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

Używam również prostego pliku binarnego jq ( http://stedolan.github.io/jq/download/ ), aby przeanalizować plik json do użycia w skryptach bash.

tslater
źródło
5
W przypadku ostatnio wdrożonych klastrów możesz zmienić v1beta3nav1
Eyal Levin,
6
Zwróć uwagę, że to polecenie curl połączy się niezabezpieczone z serwerem apiserver (umożliwiając pośrednikowi przechwycenie tokena okaziciela), więc powinieneś go używać tylko wtedy, gdy sieć między pod a serwerem jest w pełni zaufana. W przeciwnym razie należy przekazać --cacertflagę curl, aby funkcja curl zweryfikowała certyfikat przedstawiony przez apiserver.
Robert Bailey,
1
Musiałem użyć KUBERNETES_SERVICE_HOST=kubernetes.default, $KUBERNETES_443_TCP_PORT=443, NAMESPACE == $ (</ var / run / tajemnice / kubernetes.io / konto_usługi / namespace) . The URL was kubernetes.default: 443 / api / v1 / nazw / $ NAMESPACE / pods / ... `. Zauważ, że wersja API jest ustawiona na v1 zamiast v1beta3, a domyślna przestrzeń nazw została zastąpiona przez $ NAMESPACE.
ruediste
74

Każda kapsuła ma automatycznie stosowane konto usługi, które umożliwia jej dostęp do serwera apis. Konto usługi zapewnia zarówno poświadczenia klienta w postaci tokena okaziciela, jak i certyfikat urzędu certyfikacji, który został użyty do podpisania certyfikatu przedstawionego przez serwer apis. Dzięki tym dwóm informacjom możesz utworzyć bezpieczne, uwierzytelnione połączenie z apisever bez użycia curl -k(aka 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
źródło
2
Należy zauważyć, że aby cacert i token istniały na koncie usługi, kontrolerowi replikacji należy podać --root-ca-file=argument podczas uruchamiania. (jest to obsługiwane automatycznie w większości instalatorów kubernetes). Zobacz dyskusję tutaj, aby uzyskać więcej informacji: github.com/kubernetes/kubernetes/issues/10265
JKnight
7
Uzyskiwałem dostęp do serwera API z kapsuły z inną przestrzenią nazw. Tak więc musiałem używać https://kubernetes.default/jako hosta
ruediste
Oficjalny gospodarz jest kubernetes.default.svcudokumentowany na kubernetes.io/docs/tasks/access-application-cluster/ ...
Martin Tapp
17

Korzystanie z klienta Python kubernetes.

from kubernetes import client, config

config.load_incluster_config()
v1_core = client.CoreV1Api()
rix
źródło
1
Dzięki! Oto małe repozytorium z przykładem, opartym na twojej odpowiedzi, aby ułatwić zabawę z tym kodem.
Omer Levi Hevroni
10

wersja 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
źródło
6

Najważniejszym dodatkiem do szczegółów już wspomnianych powyżej jest to, że pod, z którego próbujesz uzyskać dostęp do serwera API, powinien mieć uprawnienia RBAC, aby to zrobić.

Każdy podmiot w systemie k8s jest identyfikowany przez konto usługi (jak konto użytkownika używane dla użytkowników). W oparciu o możliwości RBAC token konta usługi (/var/run/secrets/kubernetes.io/serviceaccount/token) jest wypełniany. Wiązania kube-api (np. Pykube) mogą przyjąć ten token jako dane wejściowe podczas tworzenia połączenia z serwerami kube-api. Jeśli moduł ma odpowiednie możliwości RBAC, będzie mógł nawiązać połączenie z serwerem kube-api.

pr-pal
źródło
5

Napotkałem ten problem, próbując uzyskać dostęp do API z wnętrza poda za pomocą Go Code. Poniżej przedstawiam, co wdrożyłem, aby to działało, gdyby ktoś napotkał to pytanie, chcąc również użyć Go.

W przykładzie użyto zasobu pod, dla którego należy używać client-gobiblioteki, jeśli pracujesz z natywnymi obiektami Kubernetes. Kod jest bardziej przydatny dla osób pracujących z 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
źródło
4

Dostęp do serwera API kubernetes można uzyskać bezpośrednio pod adresem „ https: //kubernetes.default ”. Domyślnie używa „domyślnego konta usługi” do uzyskiwania dostępu do serwera API.

Dlatego też musimy przekazać „ca cert” i „domyślny token konta usługi” w celu uwierzytelnienia na serwerze API.

Plik certyfikatu jest przechowywany w następującej lokalizacji w pod: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt

i domyślny token konta usługi pod adresem: /var/run/secrets/kubernetes.io/serviceaccount/token

Możesz użyć klienta GoDaddy 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
źródło
3

Miałem podobny problem z uwierzytelnianiem w GKE, gdzie skrypty Pythona nagle rzucały wyjątki. Rozwiązaniem, które zadziałało dla mnie, było udzielenie zgody na pody poprzez 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

aby uzyskać więcej informacji, wprowadź opis linku tutaj

Gumowa kaczuszka
źródło
0
curl -v -cacert <path to>/ca.crt --cert <path to>/kubernetes-node.crt --key <path to>/kubernetes-node.key https://<ip:port>

Moja wersja k8s to 1.2.0, aw innych wersjach też ma działać ^ ^

Rano Y
źródło
Powyższe jest poprawne, jeśli masz włączone webhooki lub inne włączone RBAC. Jest to szczególnie prawdziwe> 1,2 z k8s
doktoroblivion
0

This is from the Kubernetes w akcji book.

Musisz zadbać o uwierzytelnianie . Sam serwer API mówi, że nie masz do niego dostępu, ponieważ nie wie, kim jesteś .

Do uwierzytelnienia potrzebny jest token uwierzytelniania. Na szczęście token jest dostarczany za pośrednictwem wspomnianego wcześniej Secret tokena domyślnego i jest przechowywany w pliku tokenu w tajnym woluminie.

Zamierzasz użyć tokena, aby uzyskać dostęp do serwera API . Najpierw załaduj token do zmiennej środowiskowej:

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

Token jest teraz przechowywany w zmiennej środowiskowej TOKEN . Możesz go używać podczas wysyłania zapytań do serwera 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
źródło