2017年一月月 发布的文章

以Kubeadm方式安装的Kubernetes集群的探索

当前手上有两个Kubernetes cluster,一个是采用kube-up.sh安装的k8s 1.3.7版本,另外一个则是采用kubeadm安装的k8s 1.5.1版本。由于1.3.7版本安装在前,并且目前它也是承载了我们PaaS平台的环境,因此对于这个版本的Kubernetes安装环境、配置操作、日志查看、集群操作等相对较为熟悉。而Kubeadm安装的1.5.1版本K8s集群在组件部署、配置、日志等诸多方面与1.3.7版本有了较大差异。刚上手的时候,你会发现你原来所熟知的1.3.7的东西都不在原先的位置上了。估计很多和我一样,采用kubeadm将集群升级到1.5.1版本的朋友们都会遇到这类问题,于是这里打算对Kubeadm方式安装的Kubernetes集群进行一些小小的探索,把一些变动较大的点列出来,供大家参考。

一、环境

这里使用的依然是文章《使用Kubeadm安装Kubernetes》中安装完毕的Kubernetes 1.5.1集群环境,底层是阿里云ECS,操作系统是Ubuntu 16.04.1。网络用的是weave network

试验集群只有两个Node:一个master node和一个minion node。但Master node由于被taint了,因此它与minion node一样参与集群调度和承担负载。

二、核心组件的Pod化

Kubeadm安装的k8s集群与kube-up.sh安装集群相比,最大的不同应该算是kubernetes核心组件的Pod化,即:kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、kube-discovery以及etcd等核心组件都运行在集群中的Pod里的,这颇有些CoreOS的风格。只有一个组件是例外的,那就是负责在node上与本地容器引擎交互的Kubelet。

K8s的核心组件Pod均放在kube-system namespace中,通过kubectl(on master node)可以查看到:

# kubectl get pods -n kube-system
NAME                                    READY     STATUS    RESTARTS   AGE
etcd-iz25beglnhtz                       1/1       Running   2          26d
kube-apiserver-iz25beglnhtz             1/1       Running   3          26d
kube-controller-manager-iz25beglnhtz    1/1       Running   2          26d
kube-scheduler-iz25beglnhtz             1/1       Running   4          26d
... ...

另外细心的朋友可能会发现,这些核心组建的Pod名字均以所在Node的主机名为结尾,比如:kube-apiserver-iz25beglnhtz中的”iz25beglnhtz”就是其所在Node的主机名。

不过,即便这些核心组件是一个容器的形式运行在集群中,组件所使用网络依然是所在Node的主机网络,而不是Pod Network

# docker ps|grep apiserver
98ea64bbf6c8        gcr.io/google_containers/kube-apiserver-amd64:v1.5.1            "kube-apiserver --ins"   10 days ago         Up 10 days                              k8s_kube-apiserver.6c2e367b_kube-apiserver-iz25beglnhtz_kube-system_033de1afc0844729cff5e100eb700a81_557d1fb2
4f87d22b8334        gcr.io/google_containers/pause-amd64:3.0                        "/pause"                 10 days ago         Up 10 days                              k8s_POD.d8dbe16c_kube-apiserver-iz25beglnhtz_kube-system_033de1afc0844729cff5e100eb700a81_5931e490

# docker inspect 98ea64bbf6c8
... ...
"HostConfig": {
"NetworkMode": "container:4f87d22b833425082be55851d72268023d41b50649e46c738430d9dfd3abea11",
}
... ...

# docker inspect 4f87d22b833425082be55851d72268023d41b50649e46c738430d9dfd3abea11
... ...
"HostConfig": {
"NetworkMode": "host",
}
... ...

从上面docker inspect的输出可以看出kube-apiserver pod里面的pause容器采用的网络模式是host网络,而以pause容器网络为基础的kube-apiserver 容器显然就继承了这一network namespace。因此从外面看,访问Kube-apiserver这样的组件和以前没什么两样:在Master node上可以通过localhost:8080访问;在Node外,可以通过master_node_ip:6443端口访问。

三、核心组件启动配置调整

在kube-apiserver等核心组件还是以本地程序运行在物理机上的时代,修改kube-apiserver的启动参数,比如修改一下–service-node-port-range的范围、添加一个–basic-auth-file等,我们都可以通过直接修改/etc/default/kube-apiserver(以Ubuntu 14.04为例)文件的内容并重启kube-apiserver service(service restart kube-apiserver)的方式实现。其他核心组件:诸如:kube-controller-manager、kube-proxy和kube-scheduler均是如此。

但在kubeadm时代,这些配置文件不再存在,取而代之的是和用户Pod描述文件类似的manifest文件(都放置在/etc/kubernetes/manifests下面):

/etc/kubernetes/manifests# ls
etcd.json  kube-apiserver.json  kube-controller-manager.json  kube-scheduler.json

我们以为kube-apiserver增加一个启动参数:”–service-node-port-range=80-32767″ 为例:

打开并编辑/etc/kubernetes/manifests/kube-apiserver.json,在“command字段对应的值中添加”–service-node-port-range=80-32767″:

"containers": [
      {
        "name": "kube-apiserver",
        "image": "gcr.io/google_containers/kube-apiserver-amd64:v1.5.1",
        "command": [
          "kube-apiserver",
          "--insecure-bind-address=127.0.0.1",
          "--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
          "--service-cluster-ip-range=10.96.0.0/12",
          "--service-account-key-file=/etc/kubernetes/pki/apiserver-key.pem",
          "--client-ca-file=/etc/kubernetes/pki/ca.pem",
          "--tls-cert-file=/etc/kubernetes/pki/apiserver.pem",
          "--tls-private-key-file=/etc/kubernetes/pki/apiserver-key.pem",
          "--token-auth-file=/etc/kubernetes/pki/tokens.csv",
          "--secure-port=6443",
          "--allow-privileged",
          "--advertise-address=10.47.217.91",
          "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
          "--anonymous-auth=false",
          "--etcd-servers=http://127.0.0.1:2379",
          "--service-node-port-range=80-32767"
        ],

注意:不要忘记在–etcd-servers这一行后面添加一个逗号,否则kube-apiserver会退出。

修改后,你会发现kube-apiserver会被自动重启。这是kubelet的功劳。kubelet在启动时监听/etc/kubernetes/manifests目录下的文件变化并做适当处理:

# ps -ef|grep kubelet
root      1633     1  5  2016 ?        1-09:24:47 /usr/bin/kubelet --kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true --pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin --cluster-dns=10.96.0.10 --cluster-domain=cluster.local

kubelet自身是一个systemd的service,它的启动配置可以通过下面文件修改:

# cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true"
Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true"
Environment="KUBELET_NETWORK_ARGS=--network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin"
Environment="KUBELET_DNS_ARGS=--cluster-dns=10.96.0.10 --cluster-domain=cluster.local"
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_NETWORK_ARGS $KUBELET_DNS_ARGS $KUBELET_EXTRA_ARGS

四、kubectl的配置

kube-up.sh安装的k8s集群会在每个Node上的~/.kube/下创建config文件,用于kubectl访问apiserver和操作集群使用。但在kubeadm模式下,~/.kube/下面的内容变成了:

~/.kube# ls
cache/  schema/

于是有了问题1:config哪里去了?

之所以在master node上我们的kubectl依旧可以工作,那是因为默认kubectl会访问localhost:8080来与kube-apiserver交互。如果kube-apiserver没有关闭–insecure-port,那么kubectl便可以正常与kube-apiserver交互,因为–insecure-port是没有任何校验机制的。

于是又了问题2:如果是其他node上的kubectl与kube-apiserver通信或者master node上的kubectl通过secure port与kube-apiserver通信,应该如何配置?

接下来,我们一并来回答上面两个问题。kubeadm在创建集群时,在master node的/etc/kubernetes下面创建了两个文件:

/etc/kubernetes# ls -l
total 32
-rw------- 1 root root 9188 Dec 28 17:32 admin.conf
-rw------- 1 root root 9188 Dec 28 17:32 kubelet.conf
... ...

这两个文件的内容是完全一样的,仅从文件名可以看出是谁在使用。比如kubelet.conf这个文件,我们就在kubelet程序的启动参数中看到过:–kubeconfig=/etc/kubernetes/kubelet.conf

# ps -ef|grep kubelet
root      1633     1  5  2016 ?        1-09:26:41 /usr/bin/kubelet --kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true --pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin --cluster-dns=10.96.0.10 --cluster-domain=cluster.local

打开这个文件,你会发现这就是一个kubeconfig文件,文件内容较长,我们通过kubectl config view来查看一下这个文件的结构:

# kubectl --kubeconfig /etc/kubernetes/kubelet.conf config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://{master node local ip}:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: admin
  name: admin@kubernetes
- context:
    cluster: kubernetes
    user: kubelet
  name: kubelet@kubernetes
current-context: admin@kubernetes
kind: Config
preferences: {}
users:
- name: admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
- name: kubelet
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

这和我们在《Kubernetes集群Dashboard插件安装》一文中介绍的kubeconfig文件内容并不二致。不同之处就是“REDACTED”这个字样的值,我们对应到kubelet.conf中,发现每个REDACTED字样对应的都是一段数据,这段数据是由对应的数字证书内容或密钥内容转换(base64)而来的,在访问apiserver时会用到。

我们在minion node上测试一下:

minion node:

# kubectl get pods
The connection to the server localhost:8080 was refused - did you specify the right host or port?

# kubectl --kubeconfig /etc/kubernetes/kubelet.conf get pods
NAME                         READY     STATUS    RESTARTS   AGE
my-nginx-1948696469-359d6    1/1       Running   2          26d
my-nginx-1948696469-3g0n7    1/1       Running   3          26d
my-nginx-1948696469-xkzsh    1/1       Running   2          26d
my-ubuntu-2560993602-5q7q5   1/1       Running   2          26d
my-ubuntu-2560993602-lrrh0   1/1       Running   2          26d

kubeadm创建k8s集群时,会在master node上创建一些用于组件间访问的证书、密钥和token文件,上面的kubeconfig中的“REDACTED”所代表的内容就是从这些文件转化而来的:

/etc/kubernetes/pki# ls
apiserver-key.pem  apiserver.pem  apiserver-pub.pem  ca-key.pem  ca.pem  ca-pub.pem  sa-key.pem  sa-pub.pem  tokens.csv
  • apiserver-key.pem:kube-apiserver的私钥文件
  • apiserver.pem:kube-apiserver的公钥证书
  • apiserver-pub.pem kube-apiserver的公钥文件
  • ca-key.pem:CA的私钥文件
  • ca.pem:CA的公钥证书
  • ca-pub.pem :CA的公钥文件
  • sa-key.pem :serviceaccount私钥文件
  • sa-pub.pem :serviceaccount的公钥文件
  • tokens.csv:kube-apiserver用于校验的token文件

在k8s各核心组件的启动参数中会看到上面文件的身影,比如:

 kube-apiserver --insecure-bind-address=127.0.0.1 --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota --service-cluster-ip-range=10.96.0.0/12 --service-account-key-file=/etc/kubernetes/pki/apiserver-key.pem --client-ca-file=/etc/kubernetes/pki/ca.pem --tls-cert-file=/etc/kubernetes/pki/apiserver.pem --tls-private-key-file=/etc/kubernetes/pki/apiserver-key.pem --token-auth-file=/etc/kubernetes/pki/tokens.csv --secure-port=6443 --allow-privileged --advertise-address={master node local ip} --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --anonymous-auth=false --etcd-servers=http://127.0.0.1:2379 --service-node-port-range=80-32767

我们还可以在minion node上通过curl还手工测试一下通过安全通道访问master node上的kube-apiserver。在《Kubernetes集群的安全配置》一文中,我们提到过k8s的authentication(包括:客户端证书认证、basic auth、static token等)只要通过其中一个即可。当前kube-apiserver开启了客户端证书认证(–client-ca-file)和static token验证(–token-auth-file),我们只要通过其中一个,就可以通过authentication,于是我们使用static token方式。static token file的内容格式:

token,user,uid,"group1,group2,group3"

对应到master node上的tokens.csv

# cat /etc/kubernetes/pki/tokens.csv
{token},{user},812ffe41-cce0-11e6-9bd3-00163e1001d7,system:kubelet-bootstrap

我们用这个token通过curl与apiserver交互:

# curl --cacert /etc/kubernetes/pki/ca.pem -H "Authorization: Bearer {token}"  https://{master node local ip}:6443
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/apps",
    "/apis/apps/v1beta1",
    "/apis/authentication.k8s.io",
    "/apis/authentication.k8s.io/v1beta1",
    "/apis/authorization.k8s.io",
    "/apis/authorization.k8s.io/v1beta1",
    "/apis/autoscaling",
    "/apis/autoscaling/v1",
    "/apis/batch",
    "/apis/batch/v1",
    "/apis/batch/v2alpha1",
    "/apis/certificates.k8s.io",
    "/apis/certificates.k8s.io/v1alpha1",
    "/apis/extensions",
    "/apis/extensions/v1beta1",
    "/apis/policy",
    "/apis/policy/v1beta1",
    "/apis/rbac.authorization.k8s.io",
    "/apis/rbac.authorization.k8s.io/v1alpha1",
    "/apis/storage.k8s.io",
    "/apis/storage.k8s.io/v1beta1",
    "/healthz",
    "/healthz/poststarthook/bootstrap-controller",
    "/healthz/poststarthook/extensions/third-party-resources",
    "/healthz/poststarthook/rbac/bootstrap-roles",
    "/logs",
    "/metrics",
    "/swaggerapi/",
    "/ui/",
    "/version"
  ]
}

交互成功!

Kubernetes Dashboard集成Heapster

默认安装后的Kubernetes dashboard如下图所示,是无法图形化展现集群度量指标信息的:

img{512x368}

图形化展示度量指标的实现需要集成k8s的另外一个Addons组件:Heapster

Heapster原生支持K8s(v1.0.6及以后版本)和CoreOS,并且支持多种存储后端,比如:InfluxDBElasticSearchKafka等,这个风格和k8s的确很像:功能先不管完善与否,先让自己在各个平台能用起来再说^0^。这里我们使用的数据存储后端是InfluxDB。

一、安装步骤

我们的Heapster也是要放在pod里运行的。当前,Heapster的最新stable版本是v1.2.0,我们可以下载其源码包K8s cluster上的某个Node上。解压后,我们得到一个名为”heapster-1.2.0″的目录,进入该目录,我们可以看到如下内容:

root@node1:~/k8stest/dashboardinstall/heapster-1.2.0# ls
code-of-conduct.md  CONTRIBUTING.md  docs    Godeps   hooks     integration  LICENSE   metrics    riemann  version
common              deploy           events  grafana  influxdb  kafka        Makefile  README.md  vendor

InfluxDB为存储后端的Heapster部署yaml在deploy/kube-config/influxdb下面:

root@node1:~/k8stest/dashboardinstall/heapster-1.2.0# ls -l deploy/kube-config/influxdb/
total 28
-rw-r--r-- 1 root root  414 Sep 14 12:47 grafana-service.yaml
-rw-r--r-- 1 root root  942 Jan 20 15:15 heapster-controller.yaml
-rw-r--r-- 1 root root  249 Sep 14 12:47 heapster-service.yaml
-rw-r--r-- 1 root root 1465 Jan 19 21:39 influxdb-grafana-controller.yaml
-rw-r--r-- 1 root root  259 Sep 14 12:47 influxdb-service.yaml

这里有五个yaml(注意:与heapster源码库中最新的代码已经有所不同,最新代码将influxdb和grafana从influxdb-grafana-controller.yaml拆分开了)。其中的一些docker image在墙外,如果你有加速器,那么你可以直接执行create命令;否则最好找到一些替代品: 比如:用signalive/heapster_grafana:2.6.0-2替换gcr.io/google_containers/heapster_grafana:v2.6.0-2。

创建pod的操作很简单:

~/k8stest/dashboardinstall/heapster-1.2.0# kubectl create -f deploy/kube-config/influxdb/
service "monitoring-grafana" created
replicationcontroller "heapster" created
service "heapster" created
replicationcontroller "influxdb-grafana" created
service "monitoring-influxdb" created

如果image pull顺利的话,那么这些pod和service的启动是会很正常的。

//kube get pods -n kube-system
... ...
kube-system                  heapster-b1dwa                          1/1       Running   0          1h        172.16.57.9    10.46.181.146   k8s-app=heapster,version=v6
kube-system                  influxdb-grafana-8c0e0                  2/2       Running   0          1h        172.16.57.10   10.46.181.146   name=influxGrafana
... ...

我们用浏览器打开kubernetes的Dashboard,期待中的图形化和集群度量指标信息到哪里去了呢?Dashboard还是一如既往的如上面图示中那样“简朴”,显然我们遇到问题了!

二、TroubleShooting

问题在哪?我们需要逐个检视相关Pod的日志:

# kubectl logs -f pods/influxdb-grafana-xxxxxx influxdb -n kube-system
# kubectl logs -f pods/influxdb-grafana-xxxxxx grafana -n kube-system
# kubectl logs -f pods/heapster-xxxxx -n kube-system

在heapster-xxxxx这个pod中,我们发现了大量失败日志:

E0119 13:14:37.838900       1 reflector.go:203] k8s.io/heapster/metrics/heapster.go:319: Failed to list *api.Pod: the server has asked for the client to provide credentials (get pods)
E0119 13:14:37.838974       1 reflector.go:203] k8s.io/heapster/metrics/processors/node_autoscaling_enricher.go:100: Failed to list *api.Node: the server has asked for the client to provide credentials (get nodes)
E0119 13:14:37.839516       1 reflector.go:203] k8s.io/heapster/metrics/processors/namespace_based_enricher.go:84: Failed to list *api.Namespace: the server has asked for the client to provide credentials (get namespaces)

heapster无法连接apiserver,获取不要想要的信息。从kube-apiserver的日志(/var/log/upstart/kube-apiserver.log)也印证了这一点:

E0120 09:15:30.833928   12902 handlers.go:54] Unable to authenticate the request due to an error: crypto/rsa: verification error
E0120 09:15:30.834032   12902 handlers.go:54] Unable to authenticate the request due to an error: crypto/rsa: verification error
E0120 09:15:30.835324   12902 handlers.go:54] Unable to authenticate the request due to an error: crypto/rsa: verification error

从apiserver的日志来看,heapster是通过apiserver的secure port连接的,由于我们的API server设置有https client端证书校验机制,因此两者连接失败。

三、通过insecure-port连接kube-apiserver

现在我们就来解决上述问题。

首先,我们会想到:能否让heapster通过kube APIServer的insecure-port连接呢?在《Kubernetes集群的安全配置》一文中我们提到过,kube-apiserver针对insecure-port接入的请求没有任何限制机制,这样heapster就可以获取到它所想获取到的所有有用信息。

在heapster doc中的“Configuring Source”中,我们找到了连接kube-apiserver insecure-port的方法。不过在修改yaml之前,我们还是要先来看看当前heapster的一些启动配置的含义:

//deploy/kube-config/influxdb/heapster-controller.yaml
command:
        - /heapster
        - --source=kubernetes:https://kubernetes.default
        - --sink=influxdb:http://monitoring-influxdb:8086

我们看到heapster启动时有两个启动参数:
–source指示数据源,heapster是支持多种数据源的,这里用的是“kubernetes”类型的数据源,地址是:kubernetes.default。这个域名的全名是:kubernetes.default.svc.cluster.local,就是service “kubernetes”在cluster中的域名,而”kubernetes”服务就是kube-apiserver,它的信息如下:

# kubectl get services
NAME           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes     192.168.3.1     <none>        443/TCP        99d
... ...

# kubectl describe svc/kubernetes
Name:            kubernetes
Namespace:        default
Labels:            component=apiserver
            provider=kubernetes
Selector:        <none>
Type:            ClusterIP
IP:            192.168.3.1
Port:            https    443/TCP
Endpoints:        xxx.xxx.xxx.xxx:6443
Session Affinity:    ClientIP
No events.

因此,该域名在k8s DNS中会被resolve为clusterip:192.168.3.1。外加https的默认端口是443,因此实际上heapster试图访问的apiserver地址是:https://192.168.3.1:443。

heapster启动的另外一个参数是–sink,这个传入的就是存储后端,我们使用了InfluxDB,这里传入的就是上面创建的InfluxDB service的域名和端口号,我们在cluster中也能查找到该Service的信息:

# kubectl get services -n kube-system
NAME                   CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
monitoring-influxdb    192.168.3.228   <none>        8083/TCP,8086/TCP   1h
... ...

前面提到过,我们的APIServer在secure port上是有client端证书校验的,那么以这样的启动参数启动的heapster连接不上kube-apiserver就“合情合理”了。

接下来,我们按照”Configuring Source”中的方法,将heapster与kube-apiserver之间的连接方式改为通过insecure port进行:

// kube-config/influxdb/heapster-controller.yaml
... ...
command:
        - /heapster
        - --source=kubernetes:http://10.47.136.60:8080?inClusterConfig=false
        - --sink=influxdb:http://monitoring-influxdb:8086

修改后重新create。重新启动后的heapster pod的日志输出如下:

# kubectl logs -f pod/heapster-hco5i  -n kube-system
I0120 02:03:46.014589       1 heapster.go:71] /heapster --source=kubernetes:http://10.47.136.60:8080?inClusterConfig=false --sink=influxdb:http://monitoring-influxdb:8086
I0120 02:03:46.014975       1 heapster.go:72] Heapster version v1.3.0-beta.0
I0120 02:03:46.015080       1 configs.go:60] Using Kubernetes client with master "http://10.47.136.60:8080" and version v1
I0120 02:03:46.015175       1 configs.go:61] Using kubelet port 10255
E0120 02:03:46.025962       1 influxdb.go:217] issues while creating an InfluxDB sink: failed to ping InfluxDB server at "monitoring-influxdb:8086" - Get http://monitoring-influxdb:8086/ping: dial tcp 192.168.3.239:8086: getsockopt: connection refused, will retry on use
I0120 02:03:46.026090       1 influxdb.go:231] created influxdb sink with options: host:monitoring-influxdb:8086 user:root db:k8s
I0120 02:03:46.026214       1 heapster.go:193] Starting with InfluxDB Sink
I0120 02:03:46.026286       1 heapster.go:193] Starting with Metric Sink
I0120 02:03:46.051096       1 heapster.go:105] Starting heapster on port 8082
I0120 02:04:05.211382       1 influxdb.go:209] Created database "k8s" on influxDB server at "monitoring-influxdb:8086"

之前的错误消失了!

我们再次打开Dashboard查看pod信息(这里需要等上一小会儿,因为采集cluster信息也是需要时间的),我们看到集群度量指标信息以图形化的方式展现在我们面前了(可对比本文开头那幅图示):

img{512x368}

四、通过secure port连接kube-apiserver

kube-apiserver的–insecure-port更多用来调试,生产环境下可是说关就关的,因此通过kube-apiserver的secure port才是“长治久安”之道。但要如何做呢?在heapster的”Configure Source”中给了一种使用serviceaccount的方法,但感觉略有些复杂啊。这里列出一下我自己探索到的方法: 使用kubeconfig文件!在《Kubernetes集群Dashboard插件安装》一文中,我们已经配置好了kubeconfig文件(默认位置:~/.kube/config),对于kubeconfig配置项还不是很了解的童鞋可以详细参考那篇文章,这里就不赘述了。

接下来,我们来修改heapster-controller.yaml:

// deploy/kube-config/influxdb/heapster-controller.yaml

... ...
spec:
      containers:
      - name: heapster
        image: kubernetes/heapster:canary
        volumeMounts:
        - mountPath: /srv/kubernetes
          name: auth
        - mountPath: /root/.kube
          name: config
        imagePullPolicy: Always
        command:
        - /heapster
        - --source=kubernetes:https://kubernetes.default?inClusterConfig=false&insecure=true&auth=/root/.kube/config
        - --sink=influxdb:http://monitoring-influxdb:8086
      volumes:
      - name: auth
        hostPath:
          path: /srv/kubernetes
      - name: config
        hostPath:
          path: /root/.kube
... ...

从上述文件内容中–source的值我们可以看到,我们又恢复到初始kubernetes service的地址:https://kubernetes.default,但后面又跟了几个参数:

inClusterConfig=false : 不使用service accounts中的kube config信息;
insecure=true:这里偷了个懒儿:选择对kube-apiserver发过来的服务端证书做信任处理,即不校验;
auth=/root/.kube/config:这个是关键!在不使用serviceaccount时,我们使用auth文件中的信息来对应kube-apiserver的校验。

上述yaml中,我们还挂载了两个path,以便pod可以访问到相应的配置文件(~/.kube/config)和/srv/kubernetes下的证书。

保存并重新创建相关pod后,Dashboard下的集群度量指标信息依然能以图形化的方式展现出来,可见这种方法是ok的!

Kubernetes集群Dashboard插件安装

第一次利用kube-up.sh脚本方式安装Kubernetes 1.3.7集群时,我就已经顺利地将kubernetes dashboard addon安装ok了。至今在这个环境下运行十分稳定。但是毕竟是一个试验环境,有些配置是无法满足生产环境要求的,比如:安全问题。今天有时间对Dashboard的配置进行一些调整,顺带将之前Dashboard插件的安装和配置过程也记录下来,供大家参考。

一、Dashboard的默认安装步骤

1、基于默认配置项的安装

采用kube-up.sh在Ubuntu上安装dashboard的原理与安装DNS插件大同小异,主要涉及的脚本文件和配置项包括:

//  kubernetes/cluster/config-default.sh
... ...
# Optional: Install Kubernetes UI
ENABLE_CLUSTER_UI="${KUBE_ENABLE_CLUSTER_UI:-true}"
... ...

// kubernetes/cluster/ubuntu/deployAddons.sh
... ...
function deploy_dashboard {
    if ${KUBECTL} get rc -l k8s-app=kubernetes-dashboard --namespace=kube-system | grep kubernetes-dashboard-v &> /dev/null; then
        echo "Kubernetes Dashboard replicationController already exists"
    else
        echo "Creating Kubernetes Dashboard replicationController"
        ${KUBECTL} create -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-controller.yaml
    fi

    if ${KUBECTL} get service/kubernetes-dashboard --namespace=kube-system &> /dev/null; then
        echo "Kubernetes Dashboard service already exists"
    else
        echo "Creating Kubernetes Dashboard service"
        ${KUBECTL} create -f ${KUBE_ROOT}/cluster/addons/dashboard/dashboard-service.yaml
    fi

  echo
}

init

... ...

if [ "${ENABLE_CLUSTER_UI}" == true ]; then
  deploy_dashboard
fi

kube-up.sh会尝试创建”kube-system” namespace,并执行下面命令:

kubectl create -f kubernetes/cluster/addons/dashboard/dashboard-controller.yaml
kubectl create -f kubernetes/cluster/addons/dashboard/dashboard-service.yaml

这和我们在cluster中创建一个rc和service没有多大区别。

当然上面的安装方式是伴随着k8s cluster的安装进行的,如果要单独安装Dashboard,那么Dashboard主页上的安装方式显然更为简单:

kubectl create -f https://rawgit.com/kubernetes/dashboard/master/src/deploy/kubernetes-dashboard.yaml

2、调整Dashboard容器启动参数

dashboard-controller.yaml和dashboard-service.yaml两个文件内容如下:

//dashboard-controller.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: kubernetes-dashboard-v1.1.1
  namespace: kube-system
  labels:
    k8s-app: kubernetes-dashboard
    version: v1.1.1
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 1
  selector:
    k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
        version: v1.1.1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: kubernetes-dashboard
        image: gcr.io/google_containers/kubernetes-dashboard-amd64:v1.1.1
        resources:
          # keep request = limit to keep this container in guaranteed class
          limits:
            cpu: 100m
            memory: 50Mi
          requests:
            cpu: 100m
            memory: 50Mi
        ports:
        - containerPort: 9090
        livenessProbe:
          httpGet:
            path: /
            port: 9090
          initialDelaySeconds: 30
          timeoutSeconds: 30

// dashboard-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubernetes-dashboard
  namespace: kube-system
  labels:
    k8s-app: kubernetes-dashboard
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    k8s-app: kubernetes-dashboard
  ports:
  - port: 80
    targetPort: 9090

这两个文件的内容略微陈旧些,用的还是目前已不推荐使用的ReplicationController。

不过这样默认安装后,你可能还会遇到如下问题:

(1) Dashboard pod创建失败:这是由于kubernetes-dashboard-amd64:v1.1.1 image在墙外,pull image失败导致的。

可以通过使用加速器或使用替代image的方式来解决,比如:mritd/kubernetes-dashboard-amd64:v1.4.0。修改一下dashboard-controller.yaml中image那一行即可。

(2)Dashboard无法连接到master node上的api server

如果唯一的dashboard pod(由于replicas=1)被调度到minion node上,那么很可能无法连接上master node上api server(dashboard会在cluster中自动检测api server的存在,但有时候会失败),导致页面无法正常显示。因此,需要指定一下api server的url,比如:我们在dashboard-controller.yaml中为container启动增加一个启动参数–apiserver-host:

// dashboard-controller.yaml
... ...
spec:
      containers:
      - name: kubernetes-dashboard
        image: mritd/kubernetes-dashboard-amd64:v1.4.0
        imagePullPolicy: Always
        ports:
        - containerPort: 9090
          protocol: TCP
        args:
           - --apiserver-host=http://{api server host}:{api server insecure-port}
... ...

(3)增加nodeport,提供外部访问路径

dashboard以cluster service的角色运行在cluster中,我们虽然可以在Node上访问该service或直接访问pod,但要想在外部网络访问到dashboard,还需要另外设置,比如:设置nodeport。

在dashboard-service.yaml中,修改配置如下:

spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 9090
    nodePort: 12345

这样你就可以通过node 的public ip+nodeport访问到dashboard了。

不过这时,你的dashboard算是在“裸奔”,没有任何安全可言:
- dashboard ui没有访问管理机制,任何access都可以全面接管dashboard;
- 同时在背后,dashboard通过insecure-port访问apiserver,没有使用加密机制。

二、dashboard通过kubeconfig文件信息访问apiserver

我们先来建立dashboard和apiserver之间的安全通信机制。

当前master上的kube-apiserver的启动参数如下:

// /etc/default/kube-apiserver

KUBE_APISERVER_OPTS=" --insecure-bind-address=0.0.0.0 --insecure-port=8080 --etcd-servers=http://127.0.0.1:4001 --logtostderr=true --service-cluster-ip-range=192.168.3.0/24 --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,SecurityContextDeny,ResourceQuota --service-node-port-range=80-32767 --advertise-address={master node local ip} --basic-auth-file=/srv/kubernetes/basic_auth_file --client-ca-file=/srv/kubernetes/ca.crt --tls-cert-file=/srv/kubernetes/server.cert --tls-private-key-file=/srv/kubernetes/server.key"

dashboard要与apiserver建立安全通信机制,务必不能使用insecure port。kubernetes apiserver默认情况下secure port也是开启的,端口为6443。同时,apiserver开启了basic auth(–basic-auth-file=/srv/kubernetes/basic_auth_file)。这样一来,dashboard光靠传入的–apiserver-host参数将无法正常访问apiserver的secure port并通过basic auth。我们需要找到另外一个option:

我们来看一下dashboard还支持哪些cmdline options:

# docker run mritd/kubernetes-dashboard-amd64:v1.4.0 /dashboard -help
Usage of /dashboard:
      --alsologtostderr value          log to standard error as well as files
      --apiserver-host string          The address of the Kubernetes Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8080. If not specified, the assumption is that the binary runs inside aKubernetes cluster and local discovery is attempted.
      --heapster-host string           The address of the Heapster Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8082. If not specified, the assumption is that the binary runs inside aKubernetes cluster and service proxy will be used.
      --kubeconfig string              Path to kubeconfig file with authorization and master location information.
      --log-flush-frequency duration   Maximum number of seconds between log flushes (default 5s)
      --log_backtrace_at value         when logging hits line file:N, emit a stack trace (default :0)
      --log_dir value                  If non-empty, write log files in this directory
      --logtostderr value              log to standard error instead of files (default true)
      --port int                       The port to listen to for incoming HTTP requests (default 9090)
      --stderrthreshold value          logs at or above this threshold go to stderr (default 2)
  -v, --v value                        log level for V logs
      --vmodule value                  comma-separated list of pattern=N settings for file-filtered logging

从输出的options来看,只有–kubeconfig这个能够满足需求。

1、kubeconfig文件介绍

采用kube-up.sh脚本进行kubernetes默认安装后,脚本会在每个Cluster node上创建一个~/.kube/config文件,该kubeconfig文件可为k8s cluster中的组件(比如kubectl等)、addons(比如dashboard等)提供跨全cluster的安全验证机制。

下面是我的minion node上的kubeconfig文件

# cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /srv/kubernetes/ca.crt
    server: https://{master node local ip}:6443
  name: ubuntu
contexts:
- context:
    cluster: ubuntu
    namespace: default
    user: admin
  name: ubuntu
current-context: ubuntu
kind: Config
preferences: {}
users:
- name: admin
  user:
    password: {apiserver_password}
    username: {apiserver_username}
    client-certificate: /srv/kubernetes/kubecfg.crt
    client-key: /srv/kubernetes/kubecfg.key

kubeconfig中存储了clusters、users、contexts信息,以及其他一些杂项,并通过current-context指定当前context。通过该配置文件,类似kubectl这样的cluster操作工具可以很容易的在各个cluster之间切换context。一个context就是一个三元组:{cluster、user、namespace},current-context指定当前选定的context,比如上面的kubeconfig文件,当我们执行kubectl时,kubectl会读取该配置文件,并以current-context指定的那个context中的信息去查找user和cluster。这里current-context是ubuntu。

ubuntu这个context三元组中的信息是:

{
    cluster = ubuntu
    namespace = default
    user = admin
}

之后kubectl到clusters中找到name为ubuntu的cluster,发现其server为https://{master node local ip}:6443,以及其CA信息;到users中找到name为admin的user,并使用该user下的信息:

    password: {apiserver_password}
    username: {apiserver_username}
    client-certificate: /srv/kubernetes/kubecfg.crt
    client-key: /srv/kubernetes/kubecfg.key

通过kubectl config命令可以配置kubeconfig文件,具体命令可以参考这里

另外上面的/srv/kubernetes/ca.crt、/srv/kubernetes/kubecfg.crt和/srv/kubernetes/kubecfg.key都是kube-up.sh在安装k8s 1.3.7时在各个node上创建的,可以直接用来作为访问apiserver的参数传递给kubectl或其他要访问apiserver的组件或addons。

2、修改dashboard启动参数,使用kubeconfig文件

现在我们要让dashboard使用kubeconfig文件,我们需要修改dashboard-controller.yaml文件中涉及containers的配置信息:

spec:
      containers:
      - name: kubernetes-dashboard
        image: mritd/kubernetes-dashboard-amd64:v1.4.0
        volumeMounts:
          - mountPath: /srv/kubernetes
            name: auth
          - mountPath: /root/.kube
            name: config
        imagePullPolicy: Always
        ports:
        - containerPort: 9090
          protocol: TCP
        args:
           - --kubeconfig=/root/.kube/config
        livenessProbe:
          httpGet:
            path: /
            port: 9090
          initialDelaySeconds: 30
          timeoutSeconds: 30
      volumes:
      - name: auth
        hostPath:
          path: /srv/kubernetes
      - name: config
        hostPath:
          path: /root/.kube

由于要用到各种证书以及kubeconfig,我们在pod里挂载了host主机的path: /root/.kube和/srv/kubernetes。

重新部署dashboard后,dashboard与kube-apiserver之间就有了安全保障了(https+basic_auth)。

三、实现dashboard UI login

虽然上面实现了dashboard与apiserver之间的安全通道和basic auth,但通过nodeport方式访问dashboard,我们依旧可以掌控dashboard,而dashboard依旧没有任何访问控制机制。而实际情况是dashboard目前还不支持identity and access management,不过在不久的将来,dashboard将添加这方面的支持

那么在当前版本下,如何实现一个简易的login流程呢?除了前面提到的nodeport方式访问dashboard UI外,官方在trouble shooting里还提供了另外两种访问dashboard的方法,我们一起来看看是否能满足我们的最低级需求^0^。

1、kubectl proxy方式

kubectl proxy的方式默认只允许local network访问,但是kubectl proxy提供了若干flag options可以设置,我们来试试:

我们在minion node上执行:

# kubectl proxy --address='0.0.0.0' --port=30099
Starting to serve on [::]:30099

我们在minion node上的30099端口提供外网服务。打开浏览器,访问: http://{minion node public ip}:30099/ui,得到如下结果:

<h3>Unauthorized</h3>

到底哪没授权呢?我们查看kubectl proxy的flag options发现下面一个疑点:

--accept-hosts='^localhost$,^127\.0\.0\.1$,^\[::1\]$': Regular expression for hosts that the proxy should accept.

显然–accept-hosts默认接受的host地址形式让我们的访问受限。重新调整配置再次执行:

# kubectl proxy --address='0.0.0.0' --port=30099 --accept-hosts='^*$'
Starting to serve on [::]:30099

再次打开浏览器,访问:http://{minion node public ip}:30099/ui

浏览器会跳转至下面的地址:

http://{minion node public ip}:30099/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard/#/workload?namespace=default

dashboard ui访问成功!不过,这种方式依旧无需你输入user/password,这不符合我们的要求。

2、直接访问apiserver方式

trouble shooting文档提供的最后一种访问方式是直接访问apiserver方式:

打开浏览器访问:

https://{master node public ip}:6443

这时浏览器会提示你:证书问题。忽略之(由于apiserver采用的是自签署的私有证书,浏览器端无法验证apiserver的server.crt),继续访问,浏览器弹出登录对话框,让你输入用户名和密码,这里我们输入apiserver —basic-auth-file中的用户名和密码,就可以成功登录apiserver,并在浏览器页面看到如下内容:

{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/apps",
    "/apis/apps/v1alpha1",
    "/apis/autoscaling",
    "/apis/autoscaling/v1",
    "/apis/batch",
    "/apis/batch/v1",
    "/apis/batch/v2alpha1",
    "/apis/extensions",
    "/apis/extensions/v1beta1",
    "/apis/policy",
    "/apis/policy/v1alpha1",
    "/apis/rbac.authorization.k8s.io",
    "/apis/rbac.authorization.k8s.io/v1alpha1",
    "/healthz",
    "/healthz/ping",
    "/logs/",
    "/metrics",
    "/swaggerapi/",
    "/ui/",
    "/version"
  ]
}

接下来,我们访问下面地址:

https://{master node public ip}:6443/ui

你会看到页面跳转到:

https://101.201.78.51:6443/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard/

我们成功进入dashboard UI中! 显然这种访问方式满足了我们对dashboard UI采用登录访问的最低需求!

三、小结

到目前为止,dashboard已经可以使用。但它还缺少metric和类仪表盘图形展示功能,这两个功能需要额外安装Heapster才能实现,不过一般功能足以满足你对k8s cluster的管理需求。




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn


文章

评论

  • 正在加载...

分类

标签

归档











更多