Kubernetes(k8s) EmptyDir、HostPath、ConfigMap和Secret等几种存储类型介绍

一个运行中的容器,缺省情况下,对文件系统的写入,都是发生在其分层文件系统的可写层的,一旦容器运行结束,所有写入都会被丢弃。因此需要对持久化支持。

Kubernetes 中通过 Volume 的方式提供对存储的支持。下面对一些常见的存储概念进行一点简要的说明。

EmptyDir

顾名思义,EmptyDir是一个空目录,他的生命周期和所属的 Pod 是完全一致的,可能读者会奇怪,那还要他做什么?EmptyDir的用处是,可以在同一 Pod 内的不同容器之间共享工作过程中产生的文件。

缺省情况下,EmptyDir 是使用主机磁盘进行存储的,也可以设置emptyDir.medium 字段的值为Memory,来提高运行速度,但是这种设置,对该卷的占用会消耗容器的内存份额。

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: gcr.io/google_containers/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}

HostPath

这种会把宿主机上的指定卷加载到容器之中,当然,如果 Pod 发生跨主机的重建,其内容就难保证了。

这种卷一般和DaemonSet搭配使用,用来操作主机文件,例如进行日志采集的 FLK 中的 FluentD 就采用这种方式,加载主机的容器日志目录,达到收集本主机所有日志的目的。

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: gcr.io/google_containers/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data

NFS/GlusterFS/CephFS/AWS/GCE 等等

作为一个容器集群,支持网络存储自然是重中之重了,Kubernetes 支持为数众多的云提供商和网络存储方案。

各种支持的方式不尽相同,例如 GlusterFS 需要创建 Endpoint,Ceph/NFS 之流就没这么麻烦了。

各种个性配置可移步参考文档。

ConfigMap 和 Secret

镜像使用的过程中,经常需要利用配置文件、启动脚本等方式来影响容器的运行方式,如果仅有少量配置,我们可以使用环境变量的方式来进行配置。然而对于一些较为复杂的配置,例如 Apache 之类,就很难用这种方式进行控制了。另外一些敏感信息暴露在 YAML 中也是不合适的。

ConfigMap 和 Secret 除了使用文件方式进行应用之外,还有其他的应用方式;这里仅就文件方式做一点说明。

例如下面的 ConfigMap,将一个存储在 ConfigMap 中的配置目录加载到卷中。

apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: gcr.io/google_containers/busybox
      command: [ "/bin/sh", "-c", "ls /etc/config/" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        # Provide the name of the ConfigMap containing the files you want
        # to add to the container
        name: special-config
  restartPolicy: Never

注意,这里的 ConfigMap 会映射为一个目录,ConfigMap 的 Key 就是文件名,每个 Value 就是文件内容,比如下面命令用一个目录创建一个 ConfigMap:

kubectl create configmap 
    game-config 
    --from-file=docs/user-guide/configmap/kubectl

创建一个 Secret:

kubectl create secret generic 
    db-user-pass --from-file=./username.txt 
    --from-file=./password.txt

使用 Volume 加载 Secret:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
  namespace: myns
spec:
  containers:
    - name: mypod
      image: redis
      volumeMounts:
        - name: foo
          mountPath: /etc/foo
          readOnly: true
  volumes:
    - name: foo
      secret:
        secretName: mysecret

可以看到 Secret 和 ConfigMap 的创建和使用是很相似的。在 RBAC 中,Secret 和 ConfigMap 可以进行分别赋权,以此限定操作人员的可见、可控权限。

PV & PVC

PersistentVolume 和 PersistentVolumeClaim 提供了对存储支持的抽象,也提供了基础设施和应用之间的分界,管理员创建一系列的 PV 提供存储,然后为应用提供 PVC,应用程序仅需要加载一个 PVC,就可以进行访问。

而 1.5 之后又提供了 PV 的动态供应。可以不经 PV 步骤直接创建 PVC。

k8s(kubernetes)kube-proxy转发模式及service端口类型介绍

配置方式

kubernetes版本大于或者等于1.2时,外部网络(即非K8S集群内的网络)访问cluster IP的办法是:

修改master的/etc/kubernetes/proxy,把KUBE_PROXY_ARGS=”“改为KUBE_PROXY_ARGS=”–proxy-mode=userspace”
启动kube-proxy服务
在核心路由设备或者源主机上添加一条路由,访问cluster IP段的路由指向到master上。

kubernetes版本小于1.2时,直接添加路由

未分类

kube-proxy转发的两种模式

kube-proxy在转发时主要有两种模式Userspace和Iptables。如下图,左侧是Userspace模式,也是kube-proxy默认的方式,所有的转发都是通过kube-proxy软件实现的;右侧是Iptables模式,所有转发都是通过Iptables内核模块实现,而kube-proxy只负责生成相应的Iptables规则。

未分类

使用Userspace模式(k8s版本为1.2之前默认模式),外部网络可以直接访问cluster IP。
使用Iptables模式(k8s版本为1.2之后默认模式),外部网络不能直接访问cluster IP。
从效率上看,Iptables会更高一些,但是需要Iptables version >=1.4.11,Iptables模式在k8s1.2版本放出。

service转发后端服务的四种类型

ClusterIP

此类型会提供一个集群内部的虚拟IP(与Pod不在同一网段),以供集群内部的pod之间通信使用。ClusterIP也是Kubernetes service的默认类型。

未分类

为了实现图上的功能主要需要以下几个组件的协同工作:

  • apiserver:在创建service时,apiserver接收到请求以后将数据存储到etcd中。
  • kube-proxy:k8s的每个节点中都有该进程,负责实现service功能,这个进程负责感知service,pod的变化,并将变化的信息写入本地的iptables中。
  • iptables:使用NAT等技术将virtualIP的流量转至endpoint中。

NodePort

NodePort模式除了使用cluster ip外,也将service的port映射到每个node的一个指定内部port上,映射的每个node的内部port都一样。
为每个节点暴露一个端口,通过nodeip + nodeport可以访问这个服务,同时服务依然会有cluster类型的ip+port。内部通过clusterip方式访问,外部通过nodeport方式访问。

未分类

loadbalance

LoadBalancer在NodePort基础上,K8S可以请求底层云平台创建一个负载均衡器,将每个Node作为后端,进行服务分发。该模式需要底层云平台(例如GCE)支持。

Ingress

Ingress,是一种HTTP方式的路由转发机制,由Ingress Controller和HTTP代理服务器组合而成。Ingress Controller实时监控Kubernetes API,实时更新HTTP代理服务器的转发规则。HTTP代理服务器有GCE Load-Balancer、HaProxy、Nginx等开源方案。

详细说明请见http://blog.csdn.net/liyingke112/article/details/77066814

未分类

service的三种端口

port

service暴露在cluster ip上的端口,:port 是提供给集群内部客户访问service的入口。

nodePort

nodePort是k8s提供给集群外部客户访问service入口的一种方式,:nodePort 是提供给集群外部客户访问service的入口。

targetPort

targetPort是pod上的端口,从port和nodePort上到来的数据最终经过kube-proxy流入到后端pod的targetPort上进入容器。

port、nodePort总结

总的来说,port和nodePort都是service的端口,前者暴露给集群内客户访问服务,后者暴露给集群外客户访问服务。从这两个端口到来的数据都需要经过反向代理kube-proxy流入后端pod的targetPod,从而到达pod上的容器内。

ubuntu 16.04 k8s(kubernetes) delve debug环境配置

环境说明

  • 系统:ubuntu 16.04
  • k8s版本:1.5.7
  • 内存:8GB

Docker安装

1. 卸载旧版本

sudo apt-get remove docker docker-engine docker.io

2. 配置仓库

apt-get update
apt-get -y install 
    apt-transport-https 
    ca-certificates 
    curl 
    software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
apt-key fingerprint 0EBFCD88
add-apt-repository 
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu 
   $(lsb_release -cs) 
   stable"

3. 安装docker ce

apt-get update
apt-cache madison docker-ce
apt-get -y install docker-ce=17.06.0~ce-0~ubuntu

etcd安装

1. 安装

ETCD_VER=v3.2.5
# choose either URL
GOOGLE_URL=https://storage.googleapis.com/etcd
GITHUB_URL=https://github.com/coreos/etcd/releases/download
DOWNLOAD_URL=${GOOGLE_URL}

rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /opt/etcd-${ETCD_VER} && mkdir -p /opt/etcd-${ETCD_VER}

curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /opt/etcd-${ETCD_VER} --strip-components=1
/opt/etcd-${ETCD_VER}/etcd --version

2. 环境变量

echo "export PATH=/opt/etcd-${ETCD_VER}:$PATH" >> /etc/profile

go安装

1. 下载go

cd /usr/local
wget https://storage.googleapis.com/golang/go1.9rc2.linux-amd64.tar.gz
tar xf go1.9rc2.linux-amd64.tar.gz
mkdir -p /home/go

2. 环境变量

echo 'export PATH=/usr/local/go/bin:$PATH' >> /etc/profile
echo "export GOPATH=/home/go" >> /etc/profile

OpenSSL安装

apt-get -y install openssl

安装k8s

安装

apt-get -y install unzip make gccgo
cd /home/go
wget https://github.com/kubernetes/kubernetes/archive/v1.5.7.zip
unzip v1.5.7.zip 
cd kubernetes-1.5.7
. /etc/profile
make GOGCFLAGS="-N -l"

启动

./hack/local-up-cluster.sh -O

配置

export KUBERNETES_PROVIDER=local
cluster/kubectl.sh config set-cluster local --server=https://localhost:6443 --certificate-authority=/var/run/kubernetes/apiserver.crt
cluster/kubectl.sh config set-credentials myself --username=admin --password=admin
cluster/kubectl.sh config set-context local --cluster=local --user=myself
cluster/kubectl.sh config use-context local
cluster/kubectl.sh

安装delve

go get github.com/derekparker/delve/cmd/dlv
cd /home/go/src/github.com/derekparker/delve
echo 'export PATH="$GOPATH/bin:$PATH' >> /etc/profile
. /etc/profile

dlv debug

启动

dlv exec /home/go/kubernetes-1.5.7/_output/bin/kubectl -- run my-nginx --image=nginx --replicas=2 --port=80

设置断点

(dlv) b cmd/run.go:138
Breakpoint 1 set at 0x1c3a45b for k8s.io/kubernetes/pkg/kubectl/cmd.Run() ./kubernetes-1.5.7/_output/local/go/src/k8s.io/kubernetes/pkg/kubectl/cmd/run.go:138

前进到断点

(dlv) c
> k8s.io/kubernetes/pkg/kubectl/cmd.Run() ./kubernetes-1.5.7/_output/local/go/src/k8s.io/kubernetes/pkg/kubectl/cmd/run.go:138 (hits goroutine(1):1 total:1) (PC: 0x1c3a45b)
   133:         cmd.Flags().String("service-overrides", "", "An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.  Only used if --expose is true.")
   134:         cmd.Flags().Bool("quiet", false, "If true, suppress prompt messages.")
   135:         cmd.Flags().String("schedule", "", "A schedule in the Cron format the job should be run with.")
   136: }
   137:
=> 138: func Run(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobra.Command, args []string, argsLenAtDash int) error {
   139:         if len(os.Args) > 1 && os.Args[1] == "run-container" {
   140:                 printDeprecationWarning("run", "run-container")
   141:         }
   142:
   143:         // Let kubectl run follow rules for `--`, see #13004 issue

Kubernetes Pod调度原理介绍

最近两周一直没有抽出时间写点Kubernetes的东西,这篇学习一下Kubernetes对Pod的调度。我们先来复习一下Kubernetes的一些基本概念。

Kubernetes的基本概念

Kubernetes是一个基于容器技术的分布式架构平台,它首先是一个开源的容器集群管理系统,又是一个分布式系统开发、运维和支撑平台。 Kubernetes为容器应用提供了服务注册和发现、负载均衡、服务部署和运行、服务滚动升级、在线扩容和缩容、资源调度、资源配额管理等功能。 可以说Kubernetes具备完备的集群管理能力,贯串分布式系统开发、测试、部署、运维监控各个环节。

Kubernetes中的绝大部分概念都抽象成Kubernetes管理的一种资源对象,下面我们一起复习一下这些基本概念:

1、Master:Master节点是Kubernetes集群的控制节点,负责整个集群的管理和控制。Master节点上包含以下组件:

  • kube-apiserver:集群控制的入口,提供HTTP REST服务
  • kube-controller-manager:Kubernetes集群中所有资源对象的自动化控制中心
  • kube-scheduler:负责Pod的调度,我们本篇将主要学一下kube-scheduler的调度功能

2、Node: Node节点是Kubernetes集群中的工作节点,Node上的工作负载由Master节点分配,工作负载主要是运行容器应用。Node节点上包含以下组件:

  • kubelet:负责Pod的创建、启动、监控、重启、销毁等工作,同时与Master节点协作,实现集群管理的基本功能。
  • kube-proxy:实现Kubernetes Service的通信和负载均衡
  • 运行运行容器化(Pod)应用

3、Pod: Pod是Kubernetes最基本的部署调度单元。每个Pod可以由一个或多个业务容器和一个根容器(Pause容器)组成。一个Pod表示某个应用的一个实例

4、ReplicaSet:是Pod副本的抽象,用于解决Pod的扩容和伸缩

5、Deployment:Deployment表示部署,在内部使用ReplicaSet来实现。可以通过Deployment来生成相应的ReplicaSet完成Pod副本的创建

6、Service:Service是Kubernetes最重要的资源对象。Kubernetes中的Service对象可以对应微服务架构中的微服务。Service定义了服务的访问入口,服务的调用者Pod通过这个地址访问Service后端的Pod副本实例。 Service通过Label Selector同后端的Pod副本建立关系,Deployment保证后端Pod副本的数量,也就是保证服务的伸缩性

kube-scheduler调度过程

Master节点上的kube-scheduler负责Pod的调度,kube-scheduler将Pod安置到目标Node上,之后将Pod交给目标Node上的kubelet,Pod生命周期后续的部分由kubelet接管。

kube-scheduler使用特定的调度算法和调度策略将等待调度的Pod调度到某个合适的Node上。等待调度的Pod包含使用API创建的Pod,也包含ControllerManager为补足副本而创建的Pod。具体过程为kube-scheduler会从待调度Pod列表中取出每个Pod,并根据调度算法和调度策略从Node列表中选出一个最合适的Node,将Pod和目标Node绑定(Binding),同时将绑定信息写入到etcd中。目标Node上的kubelet通过kube-apiserver监听到kube-scheduler触发的Pod和目标Node的绑定事件,就会pull镜像和启动容器。

预选(Predicates)和优选(Priorites)步骤

kube-scheduler当前提供的调度过程包含预选(Predicates)和优选(Priorites)两步:

  • 预选(Predicates):将根据配置的预选策略(Predicates Policies)过滤掉不满足这些策略的Node,剩下的Node将作为候选Node成为优选过程的输入。

  • 优选(Priorites):根据配置的优选策略(Priorities Policies)计算出每个候选Node的积分,按积分排名,得分最高的Node胜出,Pod会和该Node绑定。

kube-scheduler进程的–algorithm-provider参数用于指定调度算法,当前Kubernetes版本1.6默认配置的是DefaultProvider。 plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go中包含了默认的Predicates Policies和Priorities Policies。 Scheduler Algorithm in Kubernetes中包含全部的Predicates Policies和Priorities Policies。

另外kube-scheduler可以通过–policy-config-file参数指定想要启用的Predicates Policies和Priorities Policies。例如:

{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
    {"name" : "PodFitsHostPorts"},
    {"name" : "PodFitsResources"},
    {"name" : "NoDiskConflict"},
    {"name" : "NoVolumeZoneConflict"},
    {"name" : "MatchNodeSelector"},
    {"name" : "HostName"}
    ],
"priorities" : [
    {"name" : "LeastRequestedPriority", "weight" : 1},
    {"name" : "BalancedResourceAllocation", "weight" : 1},
    {"name" : "ServiceSpreadingPriority", "weight" : 1},
    {"name" : "EqualPriority", "weight" : 1}
    ],
"hardPodAffinitySymmetricWeight" : 10
}

Pod调度入门

接下来我们先通过几个例子来学习一下基于预选策略(Predicates Policies)和优选策略(Priorities Policies)实现的Pod调度。 我们可以使用这些策略实现将Pod调度到某个或某些特别的Node上。

我们使用前面在Kubernetes 1.6 高可用集群部署部署的集群作为试验环境。

192.168.61.11 node1
192.168.61.12 node2
192.168.61.13 node3
192.168.61.14 node4

MatchNodeSelector

MatchNodeSelector是一个预选策略,用于判断候选Node是否包含Pod的spec.nodeSelector指定的标签。

我们先来看看当前集群中的Node具有的标签:

kubectl get nodes --show-labels
NAME      STATUS    AGE       VERSION   LABELS
node1     Ready     34d       v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node1
node2     Ready     32d       v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node2
node3     Ready     32d       v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node3
node4     Ready     29d       v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node4

可以看到每个Node上都有kubernetes.io/hostname=xxx这个label,我们可以使用Pod的spec.nodeSelector指定这个label将Pod调度具体的某个Node上。例如我们创建一个如下的Deployment,nginx-deployment.yaml:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
      nodeSelector:
        kubernetes.io/hostname: node4
kubectl create -f nginx-deployment.yaml
deployment "nginx-deployment" created

kubectl get deploy
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2         2         2            2           1m

kubectl get pod -o wide
NAME                                READY     STATUS        RESTARTS   AGE       IP           NODE
nginx-deployment-1270158812-9qh1v   1/1       Running       0          1m        10.244.3.5   node4
nginx-deployment-1270158812-f5k7h   1/1       Running       0          1m        10.244.3.6   node4

可以看到这个Pod的两个副本都被调度到node4上。下面继续试验,假设我们要将Pod调度到磁盘类型为ssd并且专门用来跑Web应用的Node上。 我们先删除前面创建的Deployment:

kubectl delete deploy nginx-deployment

我们给node1, node2, node3标记如下:

kubectl label node node1 disktype=ssd apptype=web
kubectl label node node2 disktype=ssd
kubectl label node node3 disktype=ssd apptype=web
kubectl label node node4 apptype=web

kubectl get nodes --show-labels
NAME      STATUS    AGE       VERSION   LABELS
node1     Ready     34d       v1.6.2    apptype=web,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/hostname=node1
node2     Ready     32d       v1.6.2    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/hostname=node2
node3     Ready     32d       v1.6.2    apptype=web,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/hostname=node3
node4     Ready     29d       v1.6.2    apptype=web,beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=node4

修改前面的nginx-deployment.yaml文件:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
      nodeSelector:
        disktype: ssd
        apptype: web
kubectl create -f nginx-deployment.yaml
deployment "nginx-deployment" created

kubectl get deploy
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2         2         2            0           45s

kubectl get pod -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP            NODE
nginx-deployment-908661896-pzs2z   1/1       Running   0          2m        10.244.0.69   node1
nginx-deployment-908661896-wvd9p   1/1       Running   0          2m        10.244.2.51   node3

可以看到Pod被调度到了node1和node3上,而node1和node3上我们前面标记了disktype=ssd和apptype=web。

NodeAffinityPriority

NodeAffinityPriority是一个优选策略,是Kubernetes 1.2开始提供的Kubernetes调度中的亲和性机制。NodeAffinityPriority的Node选择器不再限于对Node Label的精确匹配,而支持多种操作符(如In, NotIn, Exists, DoesNotExist, Gt, Lt)。支持两种类型的选择器,一种是requiredDuringSchedulingIgnoredDuringExecution,它保证所选的Node必须满足Pod对Node的所有要求,这种更像前面的MatchNodeSelector;另外一种是preferresDuringSchedulingIgnoredDuringExecution,它对kube-scheduler提出需求,kube-scheduler会尽量但不保证满足NodeSelector的要求。

例如:

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: failure-domain.beta.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: gcr.io/google_containers/pause:2.0

解决Kubernetes 1.6.4 Dashboard无法访问的问题

前一段时间将之前采用kubeadm安装的Kubernetes 1.5.1环境升级到了1.6.4版本,升级过程较为顺利。由于该k8s cluster是一个测试环境,当时并没有过于关注,就忙别的事情了。最近项目组打算在这个环境下做一些事情,而当我们重新“捡起”这个环境时,发现Kubernetes Dashboard无法访问了。

Kubernetes的dashboard可以有很多种访问方式,比如:可以通过暴露nodeport的方式(无身份验证,不安全)、可以通过访问apiserver的api服务的方式等。我们的Dashboard通过APIServer进行访问:

https://apiserver_ip:secure_port/ui

正常情况下通过浏览器访问:https://apiserver_ip:secure_port/ui,浏览器会弹出身份验证对话框,待输入正确的用户名和密码后,便可成功进入Dashboard了。但当前,我们得到的结果却是:

User "system:anonymous" cannot proxy services in the namespace "kube-system".

而访问apiserver(https://apiserver_ip:secure_port/)得到的结果如下:

User "system:anonymous" cannot get  at the cluster scope.

一、问题原因分析

k8s 1.6.x版本与1.5.x版本的一个很大不同在于1.6.x版本启用了RBAC的Authorization mode(授权模型),这点在K8s master init的日志中可以得到证实:

# kubeadm init --apiserver-advertise-address xx.xx.xx
... ...
[init] Using Kubernetes version: v1.6.4
[init] Using Authorization mode: RBAC
[preflight] Running pre-flight checks
[preflight] Starting the kubelet service
[certificates] Generated CA certificate and key.
[certificates] Generated API server certificate and key
.... ...
[apiconfig] Created RBAC rules
[addons] Created essential addon: kube-proxy
[addons] Created essential addon: kube-dns

Your Kubernetes master has initialized successfully!
... ...

在《Kubernetes集群的安全配置》一文中我们提到过Kubernetes API server的访问方法:

Authentication(身份验证) -> Authorization(授权)-> Admission Control(入口条件控制)

只不过在Kubernetes 1.5.x及以前的版本中,Authorization的环节都采用了默认的配置,即”AlwaysAllow”,对访问APIServer并不产生什么影响:

# kube-apiserver -h
... ...
--authorization-mode="AlwaysAllow": Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: AlwaysAllow,AlwaysDeny,ABAC,Webhook,RBAC
... ...

但K8s 1.6.x版本中,–authorization-mode的值发生了变化:

# cat /etc/kubernetes/manifests/kube-apiserver.yaml

spec:
  containers:
  - command:
    - kube-apiserver
    - --allow-privileged=true
    ... ...
    - --basic-auth-file=/etc/kubernetes/basic_auth_file
    - --authorization-mode=RBAC
    ... ...

注:这里我们依旧通过basic auth方式进行apiserver的Authentication,而不是用客户端数字证书校验等其他方式。

显然问题的原因就在于这里RBAC授权方式的使用,让我们无法正常访问Dashboard了。

二、Kubernetes RBAC Authorization简介

RBAC Authorization的基本概念是Role和RoleBinding。Role是一些permission的集合;而RoleBinding则是将Role授权给某些User、某些Group或某些ServiceAccount。K8s官方博客《RBAC Support in Kubernetes》一文的中的配图对此做了很生动的诠释:

从上图中我们可以看到:

Role: pod-reader 拥有Pod的get和list permissions;
RoleBinding: pod-reader 将Role: pod-reader授权给右边的User、Group和ServiceAccount。

和Role和RoleBinding对应的是,K8s还有ClusterRole和ClusterRoleBinding的概念,它们不同之处在于:ClusterRole和ClusterRoleBinding是针对整个Cluster范围内有效的,无论用户或资源所在的namespace是什么;而Role和RoleBinding的作用范围是局限在某个k8s namespace中的。

Kubernetes 1.6.4安装时内建了许多Role/ClusterRole和RoleBinds/ClusterRoleBindings:

# kubectl get role -n kube-system
NAME                                        AGE
extension-apiserver-authentication-reader   50d
system:controller:bootstrap-signer          50d
system:controller:token-cleaner             50d

# kubectl get rolebinding -n kube-system
NAME                                 AGE
system:controller:bootstrap-signer   50d
system:controller:token-cleaner      50d

# kubectl get clusterrole
NAME                                           AGE
admin                                          50d
cluster-admin                                  50d
edit                                           50d
system:auth-delegator                          50d
system:basic-user                              50d
system:controller:attachdetach-controller      50d
... ...
system:discovery                               50d
system:heapster                                50d
system:kube-aggregator                         50d
system:kube-controller-manager                 50d
system:kube-dns                                50d
system:kube-scheduler                          50d
system:node                                    50d
system:node-bootstrapper                       50d
system:node-problem-detector                   50d
system:node-proxier                            50d
system:persistent-volume-provisioner           50d
view                                           50d
weave-net                                      50d

# kubectl get clusterrolebinding
NAME                                           AGE
cluster-admin                                  50d
kubeadm:kubelet-bootstrap                      50d
kubeadm:node-proxier                           50d
kubernetes-dashboard                           50d
system:basic-user                              50d
system:controller:attachdetach-controller      50d
... ...
system:controller:statefulset-controller       50d
system:controller:ttl-controller               50d
system:discovery                               50d
system:kube-controller-manager                 50d
system:kube-dns                                50d
system:kube-scheduler                          50d
system:node                                    50d
system:node-proxier                            50d
weave-net                                      50d

三、Dashboard的role和rolebinding

Kubernetes 1.6.x启用RBAC后,诸多周边插件也都推出了适合K8s 1.6.x的manifest描述文件,比如:weave-net等。Dashboard的manifest文件中也增加了关于rolebinding的描述,我当初用的是1.6.1版本,文件内容摘录如下:

// kubernetes-dashboard.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system
... ...

我们看到在kubernetes-dashboard.yaml中,描述文件新建了一个ClusterRoleBinding:kubernetes-dashboard。该binding将ClusterRole: cluster-admin授权给了一个ServiceAccount: kubernetes-dashboard。我们看看ClusterRole: cluster-admin都包含了哪些permission:

# kubectl get clusterrole/cluster-admin -o yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  creationTimestamp: 2017-05-30T14:06:39Z
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: cluster-admin
  resourceVersion: "11"
  selfLink: /apis/rbac.authorization.k8s.io/v1beta1/clusterrolescluster-admin
  uid: 331c79dc-4541-11e7-bc9a-12584ec3a8c9
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - '*'
- nonResourceURLs:
  - '*'
  verbs:
  - '*'

可以看到,在rules设定中,cluster-admin似乎拥有了“无限”权限。不过注意:这里仅仅授权给了一个service account,并没有授权给user或group。并且这里的kubernetes-dashboard是dashboard访问apiserver时使用的(下图右侧流程),并不是user访问APIServer时使用的。

我们需要给登录dashboard或者说apiserver的user(图左侧)进行授权。

四、为user: admin进行授权

我们的kube-apiserver的启动参数中包含:

    - --basic-auth-file=/etc/kubernetes/basic_auth_file

也就是说我们访问apiserver使用的是basic auth的身份验证方式,而user恰为admin。而从本文开头的错误现象来看,admin这个user并未得到足够的授权。这里我们要做的就是给admin选择一个合适的clusterrole。但kubectl并不支持查看user的信息,初始的clusterrolebinding又那么多,一一查看十分麻烦。我们知道cluster-admin这个clusterrole是全权限的,我们就来将admin这个user与clusterrole: cluster-admin bind到一起:

# kubectl create clusterrolebinding login-on-dashboard-with-cluster-admin --clusterrole=cluster-admin --user=admin
clusterrolebinding "login-on-dashboard-with-cluster-admin" created

# kubectl get clusterrolebinding/login-on-dashboard-with-cluster-admin -o yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  creationTimestamp: 2017-07-20T08:57:07Z
  name: login-on-dashboard-with-cluster-admin
  resourceVersion: "5363564"
  selfLink: /apis/rbac.authorization.k8s.io/v1beta1/clusterrolebindingslogin-on-dashboard-with-cluster-admin
  uid: 686a3f36-6d29-11e7-8f69-00163e1001d7
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: admin

binding后,我们再来访问一下dashboard UI,不出意外的话,熟悉的dashboard界面就会出现在你的眼前。

用curl测试结果如下:

$curl -u admin:YOUR_PASSWORD -k https://apiserver_ip:secure_port/
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/apps",
    "/apis/apps/v1beta1",
    "/apis/authentication.k8s.io",
    "/apis/authentication.k8s.io/v1",
    "/apis/authentication.k8s.io/v1beta1",
    "/apis/authorization.k8s.io",
    "/apis/authorization.k8s.io/v1",
    "/apis/authorization.k8s.io/v1beta1",
    "/apis/autoscaling",
    "/apis/autoscaling/v1",
    "/apis/autoscaling/v2alpha1",
    "/apis/batch",
    "/apis/batch/v1",
    "/apis/batch/v2alpha1",
    "/apis/certificates.k8s.io",
    "/apis/certificates.k8s.io/v1beta1",
    "/apis/extensions",
    "/apis/extensions/v1beta1",
    "/apis/policy",
    "/apis/policy/v1beta1",
    "/apis/rbac.authorization.k8s.io",
    "/apis/rbac.authorization.k8s.io/v1alpha1",
    "/apis/rbac.authorization.k8s.io/v1beta1",
    "/apis/settings.k8s.io",
    "/apis/settings.k8s.io/v1alpha1",
    "/apis/storage.k8s.io",
    "/apis/storage.k8s.io/v1",
    "/apis/storage.k8s.io/v1beta1",
    "/healthz",
    "/healthz/ping",
    "/healthz/poststarthook/bootstrap-controller",
    "/healthz/poststarthook/ca-registration",
    "/healthz/poststarthook/extensions/third-party-resources",
    "/healthz/poststarthook/rbac/bootstrap-roles",
    "/logs",
    "/metrics",
    "/swaggerapi/",
    "/ui/",
    "/version"
  ]
}

k8s(kubernetes)安全控制认证与授权

kubernetes 对于访问 API 来说提供了两个步骤的安全措施:认证和授权。认证解决用户是谁的问题,授权解决用户能做什么的问题。通过合理的权限管理,能够保证系统的安全可靠。

通俗的讲,认证就是验证用户名密码,授权就是检查该用户是否拥有权限访问请求的资源。

Kubernetes集群的所有操作基本上都是通过kube-apiserver这个组件进行的,它提供HTTP RESTful形式的API供集群内外客户端调用。需要注意的是:认证授权过程只存在HTTPS形式的API中。也就是说,如果客户端使用HTTP连接到kube-apiserver,那么是不会进行认证授权的。所以说,可以这么设置,在集群内部组件间通信使用HTTP,集群外部就使用HTTPS,这样既增加了安全性,也不至于太复杂。

下图是 API 访问要经过的三个步骤,前面两个是认证和授权,第三个是 Admission Control,它也能在一定程度上提高安全性,不过更多是资源管理方面的作用。

未分类

下面将介绍1.6版本中已经支持的一些认证方式。

客户端证书

客户端证书认证叫作TLS双向认证,也就是服务器客户端互相验证证书的正确性,在都正确的情况下协调通信加密方案。

为了使用这个方案,api-server需要用-client-ca-file=选项来开启。CA_CERTIFICATE_FILE肯定包括一个或者多个认证中心,可以被用来验证呈现给api-server的客户端证书。客户端证书的/CN将作为用户名。

静态Token文件

用token唯一标识请求者,只要apiserver存在该token,则认为认证通过,但是如果需要新增Token,则需要重启kube-apiserver组件,实际效果不是很好。

当在命令行指定- -token-auth-file=SOMEFILE选项时,API服务器从文件中读取 bearer tokens。目前,tokens持续无限期。

令牌文件是一个至少包含3列的csv文件: token, user name, user uid,后跟可选的组名。注意,如果您有多个组,则列必须是双引号,例如:

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

当通过客户端使用 bearer token 认证时,API服务器需要一个值为Bearer THETOKEN的授权头。bearer token必须是,可以放在HTTP请求头中且值不需要转码和引用的一个字符串。例如:如果bearer token是31ada4fd-adec-460c-809a-9e56ceb75269,它将会在HTTP头中按下面的方式呈现:

Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269

引导Token

在v1.6版本中,这个特性还是alpha特性。为了能够在新的集群中使用bootstrapping认证。Kubernetes包括一种动态管理的Bearer(持票人) token,这种token以Secrets的方式存储在kube-system命名空间中,在这个命名空间token可以被动态的管理和创建。Controller Manager有一个管理中心,如果token过期了就会删除。

创建的token证书满足[a-z0-9]{6}.[a-z0-9]{16}格式,Token的第一部分是一个Token ID,第二部分是token的秘钥。你需要在http协议头中加上类似的信息:

Authorization: Bearer 781292.db7bc3a58fc5f07e

如果要使用Bootstrap,需要在API Sever中开启–experimental-bootstrap-token-auth。同时必须在Controller Manager中开启管理中心的设置–controllers=*,tokencleaner。

在使用kubeadm部署Kubernetes时,kubeadm会自动创建默认token,可通过kubeadm token list命令查询。

静态密码文件

静态密码的方式是提前在某个文件中保存了用户名和密码的信息,然后在 apiserver 启动的时候通过参数 –basic-auth-file=SOMEFILE 指定文件的路径。apiserver 一旦启动,加载的用户名和密码信息就不会发生改变,任何对源文件的修改必须重启 apiserver 才能生效。

静态密码文件是 CSV 格式的文件,每行对应一个用户的信息,前面三列密码、用户名、用户 ID 是必须的,第四列是可选的组名(如果有多个组,必须用双引号):

password,user,uid,"group1,group2,group3"

客户端在发送请求的时候需要在请求头部添加上 Authorization 字段,对应的值是 Basic BASE64ENCODED(USER:PASSWORD) 。apiserver 解析出客户端提供的用户名和密码,如果和文件中的某一行匹配,就认为认证成功。

注意:

这种方式很不灵活,也不安全,可以说名存实亡,不推荐使用。

Service Account Tokens 认证

有些情况下,我们希望在 pod 内部访问 apiserver,获取集群的信息,甚至对集群进行改动。针对这种情况,kubernetes 提供了一种特殊的认证方式:Service Account。 Service Account 是面向 namespace 的,每个 namespace 创建的时候,kubernetes 会自动在这个 namespace 下面创建一个默认的 Service Account;并且这个 Service Account 只能访问该 namespace 的资源。Service Account 和 pod、service、deployment 一样是 kubernetes 集群中的一种资源,用户也可以创建自己的 serviceaccount。

ServiceAccount 主要包含了三个内容:namespace、Token 和 CA。namespace 指定了 pod 所在的 namespace,CA 用于验证 apiserver 的证书,token 用作身份验证。它们都通过 mount 的方式保存在 pod 的文件系统中,其中 token 保存的路径是 /var/run/secrets/kubernetes.io/serviceaccount/token ,是 apiserver 通过私钥签发 token 的 base64 编码后的结果; CA 保存的路径是 /var/run/secrets/kubernetes.io/serviceaccount/ca.crt ,namespace 保存的路径是 /var/run/secrets/kubernetes.io/serviceaccount/namespace ,也是用 base64 编码。

如果 token 能够通过认证,那么请求的用户名将被设置为 system:serviceaccount:(NAMESPACE):(SERVICEACCOUNT) ,而请求的组名有两个: system:serviceaccounts 和 system:serviceaccounts:(NAMESPACE)。

关于 Service Account 的配置可以参考官方的 Manager Service Accounts 文档。

OpenID Connect Tokens 认证

OpenID Connect 是一些由OAuth2提供商支持的OAuth2,特别是Azure Active Directory,Salesforce和Google。OAuth2的协议的主要扩展是增加一个额外字段,返回了一个叫ID token的access token。这个token是被服务器签名的JSON Web Token (JWT) ,具有众所周知的字段,比如用户的email。

为了识别用户,验证使用来自OAuth2 token响应的id_token (而不是 access_token)作为bearer token。token如何包含在请求中可以参考下图:

未分类

使用OpenID认证,API Server需要配置
– –oidc-issuer-url,如https://accounts.google.com
– –oidc-client-id,如kubernetes
– –oidc-username-claim,如sub
– –oidc-groups-claim,如groups
– –oidc-ca-file,如/etc/kubernetes/ssl/kc-ca.pem

Webhook Token 认证

Webhook Token 认证方式可以让用户使用自己的认证方式,用户只需要按照约定的请求格式和应答格式提供 HTTPS 服务,当用户把 Bearer Token 放到请求的头部,kubernetes 会把 token 发送给事先配置的地址进行认证,如果认证结果成功,则认为请求用户合法。 这种方式下有两个参数可以配置:

–authentication-token-webhook-config-file :kubeconfig 文件说明如果访问认证服务器

–authentication-token-webhook-cache-ttl :认证结果要缓存多久,默认是两分钟
这种方式下,自定义认证的请求和应答都有一定的格式,具体的规范请参考 官方文档的说明 。

认证代理

API Server需要配置

–requestheader-username-headers=X-Remote-User
–requestheader-group-headers=X-Remote-Group
–requestheader-extra-headers-prefix=X-Remote-Extra-
#为了防止头部欺骗,证书是必选项
–requestheader-client-ca-file
#设置允许的CN列表。可选。
–requestheader-allowed-names

Keystone Password 认证

Keystone 是 OpenStack 提供的认证和授权组件,这个方法对于已经使用 openstack 来搭建 Iaas 平台的公司比较适用,直接使用 keystone 可以保证 Iaas 和 Caas 平台保持一致的用户体系。

需要API Server在启动时指定–experimental-keystone-url=,而https时还需要设置–experimental-keystone-ca-file=SOMEFILE。

匿名请求

如果请求没有通过以上任何方式的认证,正常情况下应该是直接返回 401 错误。但是 kubernetes 还提供另外一种选择,给没有通过认证的请求一个特殊的用户名 system:anonymous 和组名 system:unauthenticated 。

这样的话,可以跟下面要讲的授权结合起来,为匿名请求设置一些特殊的权限,比如只能读取当前 namespace 的 pod 信息,方便用户访问。

如果使用AlwaysAllow以外的认证模式,则匿名请求默认开启,但可用–anonymous-auth=false禁止匿名请求。

手动一步步搭建k8s(Kubernetes)高可用集群

以前一直用 Kargo(基于 ansible) 来搭建 Kubernetes 集群,最近发现 ansible 部署的时候有些东西有点 bug,而且 Kargo 对 rkt 等也做了适配,感觉问题已经有点复杂化了;在 2.2 release 没出来这个时候,准备自己纯手动挡部署一下,Master HA 直接抄 Kargo 的就行了,以下记录一下

一、环境准备

以下文章本着 多写代码少哔哔 的原则,会主要以实际操作为主,不会过多介绍每步细节动作,如果纯小白想要更详细的了解,可以参考 这里

环境总共 5 台虚拟机,2 个 master,3 个 etcd 节点,master 同时也作为 node 负载 pod,在分发证书等阶段将在另外一台主机上执行,该主机对集群内所有节点配置了 ssh 秘钥登录

未分类

网络方案这里采用性能比较好的 Calico,集群开启 RBAC,RBAC 相关可参考:https://mritd.me/2017/07/17/kubernetes-rbac-chinese-translation/

二、证书相关处理

2.1、证书说明

由于 Etcd 和 Kubernetes 全部采用 TLS 通讯,所以先要生成 TLS 证书,证书生成工具采用 cfssl,具体使用方法这里不再详细阐述,生成证书时可在任一节点完成,这里在宿主机执行,证书列表如下

未分类

2.2、CFSSL 工具安装

首先下载 cfssl,并给予可执行权限,然后扔到 PATH 目录下

wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
chmod +x cfssl_linux-amd64 cfssljson_linux-amd64
mv cfssl_linux-amd64 /usr/local/bin/cfssl
mv cfssljson_linux-amd64 /usr/local/bin/cfssljson

2.3、生成 Etcd 证书

Etcd 证书生成所需配置文件如下:

  • etcd-root-ca-csr.json
{
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "O": "etcd",
      "OU": "etcd Security",
      "L": "Beijing",
      "ST": "Beijing",
      "C": "CN"
    }
  ],
  "CN": "etcd-root-ca"
}
  • etcd-gencert.json
{
  "signing": {
    "default": {
        "usages": [
          "signing",
          "key encipherment",
          "server auth",
          "client auth"
        ],
        "expiry": "87600h"
    }
  }
}
  • etcd-csr.json
{
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "O": "etcd",
      "OU": "etcd Security",
      "L": "Beijing",
      "ST": "Beijing",
      "C": "CN"
    }
  ],
  "CN": "etcd",
  "hosts": [
    "127.0.0.1",
    "localhost",
    "192.168.1.11",
    "192.168.1.12",
    "192.168.1.13",
    "192.168.1.14",
    "192.168.1.15"
  ]
}

最后生成 Etcd 证书

cfssl gencert --initca=true etcd-root-ca-csr.json | cfssljson --bare etcd-root-ca
cfssl gencert --ca etcd-root-ca.pem --ca-key etcd-root-ca-key.pem --config etcd-gencert.json etcd-csr.json | cfssljson --bare etcd

生成的证书列表如下

未分类

2.4、生成 Kubernetes 证书

Kubernetes 证书生成所需配置文件如下:

  • k8s-root-ca-csr.json
{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}
  • k8s-gencert.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
        "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ],
        "expiry": "87600h"
      }
    }
  }
}
  • kubernetes-csr.json
{
    "CN": "kubernetes",
    "hosts": [
        "127.0.0.1",
        "10.254.0.1",
        "192.168.1.11",
        "192.168.1.12",
        "192.168.1.13",
        "192.168.1.14",
        "192.168.1.15",
        "localhost",
        "kubernetes",
        "kubernetes.default",
        "kubernetes.default.svc",
        "kubernetes.default.svc.cluster",
        "kubernetes.default.svc.cluster.local"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}
  • kube-proxy-csr.json
{
  "CN": "system:kube-proxy",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}
  • admin-csr.json
{
  "CN": "admin",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "system:masters",
      "OU": "System"
    }
  ]
}

生成 Kubernetes 证书

cfssl gencert --initca=true k8s-root-ca-csr.json | cfssljson --bare k8s-root-ca

for targetName in kubernetes admin kube-proxy; do
    cfssl gencert --ca k8s-root-ca.pem --ca-key k8s-root-ca-key.pem --config k8s-gencert.json --profile kubernetes $targetName-csr.json | cfssljson --bare $targetName
done

生成后证书列表如下

未分类

2.5、生成 token 及 kubeconfig

生成 token 如下

export BOOTSTRAP_TOKEN=$(head -c 16 /dev/urandom | od -An -t x | tr -d ' ')
cat > token.csv <<EOF
${BOOTSTRAP_TOKEN},kubelet-bootstrap,10001,"system:kubelet-bootstrap"
EOF

创建 kubelet bootstrapping kubeconfig 配置,对于 node 节点,api server 地址为本地 nginx 监听的 127.0.0.1:6443,如果想把 master 也当做 node 使用,那么 master 上 api server 地址应该为 masterIP:6443,因为在 master 上没必要也无法启动 nginx 来监听 127.0.0.1:6443(6443 已经被 master 上的 api server 占用了)

所以以下配置只适合 node 节点,如果想把 master 也当做 node,那么需要重新生成下面的 kubeconfig 配置,并把 api server 地址修改为当前 master 的 api server 地址

export KUBE_APISERVER="https://127.0.0.1:6443"
# 设置集群参数
kubectl config set-cluster kubernetes 
  --certificate-authority=k8s-root-ca.pem 
  --embed-certs=true 
  --server=${KUBE_APISERVER} 
  --kubeconfig=bootstrap.kubeconfig
# 设置客户端认证参数
kubectl config set-credentials kubelet-bootstrap 
  --token=${BOOTSTRAP_TOKEN} 
  --kubeconfig=bootstrap.kubeconfig
# 设置上下文参数
kubectl config set-context default 
  --cluster=kubernetes 
  --user=kubelet-bootstrap 
  --kubeconfig=bootstrap.kubeconfig
# 设置默认上下文
kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

创建 kube-proxy kubeconfig 配置,同上面一样,如果想要把 master 当 node 使用,需要修改 api server

# 设置集群参数
kubectl config set-cluster kubernetes 
  --certificate-authority=k8s-root-ca.pem 
  --embed-certs=true 
  --server=${KUBE_APISERVER} 
  --kubeconfig=kube-proxy.kubeconfig
# 设置客户端认证参数
kubectl config set-credentials kube-proxy 
  --client-certificate=kube-proxy.pem 
  --client-key=kube-proxy-key.pem 
  --embed-certs=true 
  --kubeconfig=kube-proxy.kubeconfig
# 设置上下文参数
kubectl config set-context default 
  --cluster=kubernetes 
  --user=kube-proxy 
  --kubeconfig=kube-proxy.kubeconfig
# 设置默认上下文
kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig

三、部署 HA ETCD

3.1、安装 Etcd

ETCD 直接采用 rpm 安装,RPM 可以从 Fedora 官方仓库 获取 spec 文件自己 build,或者直接从 rpmFind 网站 搜索

# 下载 rpm 包
wget ftp://195.220.108.108/linux/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/e/etcd-3.1.9-1.fc27.x86_64.rpm
# 分发并安装 rpm
for IP in `seq 1 3`; do
    scp etcd-3.1.9-1.fc27.x86_64.rpm [email protected]$IP:~
    ssh [email protected]$IP rpm -ivh etcd-3.1.9-1.fc27.x86_64.rpm
done

3.2、分发证书

for IP in `seq 1 3`;do
    ssh [email protected]$IP mkdir /etc/etcd/ssl
    scp *.pem [email protected]$IP:/etc/etcd/ssl
    ssh [email protected]$IP chown -R etcd:etcd /etc/etcd/ssl
    ssh [email protected]$IP chmod -R 755 /etc/etcd
done

3.3、修改配置

rpm 安装好以后直接修改 /etc/etcd/etcd.conf 配置文件即可,其中单个节点配置如下(其他节点只是名字和 IP 不同)

# [member]
ETCD_NAME=etcd1
ETCD_DATA_DIR="/var/lib/etcd/etcd1.etcd"
ETCD_WAL_DIR="/var/lib/etcd/wal"
ETCD_SNAPSHOT_COUNT="100"
ETCD_HEARTBEAT_INTERVAL="100"
ETCD_ELECTION_TIMEOUT="1000"
ETCD_LISTEN_PEER_URLS="https://192.168.1.11:2380"
ETCD_LISTEN_CLIENT_URLS="https://192.168.1.11:2379,http://127.0.0.1:2379"
ETCD_MAX_SNAPSHOTS="5"
ETCD_MAX_WALS="5"
#ETCD_CORS=""

# [cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.1.11:2380"
# if you use different ETCD_NAME (e.g. test), set ETCD_INITIAL_CLUSTER value for this name, i.e. "test=http://..."
ETCD_INITIAL_CLUSTER="etcd1=https://192.168.1.11:2380,etcd2=https://192.168.1.12:2380,etcd3=https://192.168.1.13:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.1.11:2379"
#ETCD_DISCOVERY=""
#ETCD_DISCOVERY_SRV=""
#ETCD_DISCOVERY_FALLBACK="proxy"
#ETCD_DISCOVERY_PROXY=""
#ETCD_STRICT_RECONFIG_CHECK="false"
#ETCD_AUTO_COMPACTION_RETENTION="0"

# [proxy]
#ETCD_PROXY="off"
#ETCD_PROXY_FAILURE_WAIT="5000"
#ETCD_PROXY_REFRESH_INTERVAL="30000"
#ETCD_PROXY_DIAL_TIMEOUT="1000"
#ETCD_PROXY_WRITE_TIMEOUT="5000"
#ETCD_PROXY_READ_TIMEOUT="0"

# [security]
ETCD_CERT_FILE="/etc/etcd/ssl/etcd.pem"
ETCD_KEY_FILE="/etc/etcd/ssl/etcd-key.pem"
ETCD_CLIENT_CERT_AUTH="true"
ETCD_TRUSTED_CA_FILE="/etc/etcd/ssl/etcd-root-ca.pem"
ETCD_AUTO_TLS="true"
ETCD_PEER_CERT_FILE="/etc/etcd/ssl/etcd.pem"
ETCD_PEER_KEY_FILE="/etc/etcd/ssl/etcd-key.pem"
ETCD_PEER_CLIENT_CERT_AUTH="true"
ETCD_PEER_TRUSTED_CA_FILE="/etc/etcd/ssl/etcd-root-ca.pem"
ETCD_PEER_AUTO_TLS="true"

# [logging]
#ETCD_DEBUG="false"
# examples for -log-package-levels etcdserver=WARNING,security=DEBUG
#ETCD_LOG_PACKAGE_LEVELS=""

3.4、启动及验证

配置修改后在每个节点进行启动即可,注意,Etcd 哥哥节点间必须保证时钟同步,否则会造成启动失败等错误

systemctl daemon-reload
systemctl start etcd
systemctl enable etcd

启动成功后验证节点状态

export ETCDCTL_API=3
etcdctl --cacert=/etc/etcd/ssl/etcd-root-ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem --endpoints=https://192.168.1.11:2379,https://192.168.1.12:2379,https://192.168.1.13:2379 endpoint health

最后截图如下,警告可忽略

未分类

四、部署 HA Master

4.1、HA Master 简述

目前所谓的 Kubernetes HA 其实主要的就是 API Server 的 HA,master 上其他组件比如 controller-manager 等都是可以通过 Etcd 做选举;而 API Server 只是提供一个请求接收服务,所以对于 API Server 一般有两种方式做 HA;一种是对多个 API Server 做 vip,另一种使用 nginx 反向代理,本文采用 nginx 方式,以下为 HA 示意图

未分类

master 之间除 api server 以外其他组件通过 etcd 选举,api server 默认不作处理;在每个 node 上启动一个 nginx,每个 nginx 反向代理所有 api server,node 上 kubelet、kube-proxy 连接本地的 nginx 代理端口,当 nginx 发现无法连接后端时会自动踢掉出问题的 api server,从而实现 api server 的 HA

4.2、部署前预处理

一切以偷懒为主,所以我们仍然采用 rpm 的方式来安装 kubernetes 各个组件,关于 rpm 获取方式可以参考 How to build Kubernetes RPM,以下文章默认认为你已经搞定了 rpm

# 分发 rpm
for IP in `seq 1 3`; do
    scp kubernetes*.rpm [email protected]$IP:~; 
    ssh [email protected]$IP yum install -y conntrack-tools socat
    ssh [email protected]$IP rpm -ivh kubernetes*.rpm
done

rpm 安装好以后还需要进行分发证书配置等

for IP in `seq 1 3`;do
    ssh [email protected]$IP mkdir /etc/kubernetes/ssl
    scp *.pem [email protected]$IP:/etc/kubernetes/ssl
    scp *.kubeconfig [email protected]$IP:/etc/kubernetes
    scp token.csv [email protected]$IP:/etc/kubernetes
    ssh [email protected]$IP chown -R kube:kube /etc/kubernetes/ssl
done

最后由于 api server 会写入一些日志,所以先创建好相关目录,并做好授权,防止因为权限错误导致 api server 无法启动

for IP in `seq 1 3`;do
    ssh [email protected]$IP mkdir /var/log/kube-audit  
    ssh [email protected]$IP chown -R kube:kube /var/log/kube-audit
    ssh [email protected]$IP chmod -R 755 /var/log/kube-audit
done

4.3、修改 master 配置

rpm 安装好以后,默认会生成 /etc/kubernetes 目录,并且该目录中会有很多配置,其中 config 配置文件为通用配置,具体文件如下

➜  kubernetes tree
.
├── apiserver
├── config
├── controller-manager
├── kubelet
├── proxy
└── scheduler

0 directories, 6 files

master 需要编辑 config、apiserver、controller-manager、scheduler这四个文件,具体修改如下

  • config 通用配置
###
# kubernetes system config
#
# The following values are used to configure various aspects of all
# kubernetes services, including
#
#   kube-apiserver.service
#   kube-controller-manager.service
#   kube-scheduler.service
#   kubelet.service
#   kube-proxy.service
# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=2"

# Should this cluster be allowed to run privileged docker containers
KUBE_ALLOW_PRIV="--allow-privileged=true"

# How the controller-manager, scheduler, and proxy find the apiserver
KUBE_MASTER="--master=http://127.0.0.1:8080"
  • apiserver 配置(其他节点只有 IP 不同)
###
# kubernetes system config
#
# The following values are used to configure the kube-apiserver
#

# The address on the local server to listen to.
KUBE_API_ADDRESS="--advertise-address=192.168.1.11 --insecure-bind-address=127.0.0.1 --bind-address=192.168.1.11"

# The port on the local server to listen on.
KUBE_API_PORT="--insecure-port=8080 --secure-port=6443"

# Port minions listen on
# KUBELET_PORT="--kubelet-port=10250"

# Comma separated list of nodes in the etcd cluster
KUBE_ETCD_SERVERS="--etcd-servers=https://192.168.1.11:2379,https://192.168.1.12:2379,https://192.168.1.13:2379"

# Address range to use for services
KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.254.0.0/16"

# default admission control policies
KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota"

# Add your own!
KUBE_API_ARGS="--authorization-mode=RBAC 
               --runtime-config=rbac.authorization.k8s.io/v1beta1 
               --anonymous-auth=false 
               --kubelet-https=true 
               --experimental-bootstrap-token-auth 
               --token-auth-file=/etc/kubernetes/token.csv 
               --service-node-port-range=30000-50000 
               --tls-cert-file=/etc/kubernetes/ssl/kubernetes.pem 
               --tls-private-key-file=/etc/kubernetes/ssl/kubernetes-key.pem 
               --client-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
               --service-account-key-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
               --etcd-quorum-read=true 
               --storage-backend=etcd3 
               --etcd-cafile=/etc/etcd/ssl/etcd-root-ca.pem 
               --etcd-certfile=/etc/etcd/ssl/etcd.pem 
               --etcd-keyfile=/etc/etcd/ssl/etcd-key.pem 
               --enable-swagger-ui=true 
               --apiserver-count=3 
               --audit-log-maxage=30 
               --audit-log-maxbackup=3 
               --audit-log-maxsize=100 
               --audit-log-path=/var/log/kube-audit/audit.log 
               --event-ttl=1h"
  • controller-manager 配置
###
# The following values are used to configure the kubernetes controller-manager

# defaults from config and apiserver should be adequate

# Add your own!
KUBE_CONTROLLER_MANAGER_ARGS="--address=0.0.0.0 
                              --service-cluster-ip-range=10.254.0.0/16 
                              --cluster-name=kubernetes 
                              --cluster-signing-cert-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
                              --cluster-signing-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem 
                              --service-account-private-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem 
                              --root-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
                              --leader-elect=true 
                              --node-monitor-grace-period=40s 
                              --node-monitor-period=5s 
                              --pod-eviction-timeout=5m0s"
  • scheduler 配置
###
# kubernetes scheduler config

# default config should be adequate

# Add your own!
KUBE_SCHEDULER_ARGS="--leader-elect=true --address=0.0.0.0"

其他 master 节点配置相同,只需要修改以下 IP 地址即可,修改完成后启动 api server

systemctl daemon-reload
systemctl start kube-apiserver
systemctl start kube-controller-manager
systemctl start kube-scheduler
systemctl enable kube-apiserver
systemctl enable kube-controller-manager
systemctl enable kube-scheduler

各个节点启动成功后,验证组件状态(kubectl 在不做任何配置的情况下默认链接本地 8080 端口)如下,其中 etcd 全部为 Unhealthy 状态,并且提示 remote error: tls: bad certificate 这是个 bug,不影响实际使用,具体可参考 issue

未分类

五、部署 Node

5.1、部署前预处理

部署前分发 rpm 以及证书、token 等配置

# 分发 rpm
for IP in `seq 4 5`;do
    scp kubernetes-node-1.6.7-1.el7.centos.x86_64.rpm kubernetes-client-1.6.7-1.el7.centos.x86_64.rpm [email protected]$IP:~; 
    ssh [email protected]$IP yum install -y conntrack-tools socat
    ssh [email protected]$IP rpm -ivh kubernetes-node-1.6.7-1.el7.centos.x86_64.rpm kubernetes-client-1.6.7-1.el7.centos.x86_64.rpm
done
# 分发证书等配置文件
for IP in `seq 4 5`;do
    ssh [email protected]$IP mkdir /etc/kubernetes/ssl
    scp *.pem [email protected]$IP:/etc/kubernetes/ssl
    scp *.kubeconfig [email protected]$IP:/etc/kubernetes
    scp token.csv [email protected]$IP:/etc/kubernetes
    ssh [email protected]$IP chown -R kube:kube /etc/kubernetes/ssl
done

5.2、修改 node 配置

node 节点上配置文件同样位于 /etc/kubernetes 目录,node 节点只需要修改 config、kubelet、proxy 这三个配置文件,修改如下

  • config 通用配置

注意: config 配置文件(包括下面的 kubelet、proxy)中全部未 定义 API Server 地址,因为 kubelet 和 kube-proxy 组件启动时使用了 –require-kubeconfig 选项,该选项会使其从 *.kubeconfig 中读取 API Server 地址,而忽略配置文件中设置的;所以配置文件中设置的地址其实是无效的

###
# kubernetes system config
#
# The following values are used to configure various aspects of all
# kubernetes services, including
#
#   kube-apiserver.service
#   kube-controller-manager.service
#   kube-scheduler.service
#   kubelet.service
#   kube-proxy.service
# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=2"

# Should this cluster be allowed to run privileged docker containers
KUBE_ALLOW_PRIV="--allow-privileged=true"

# How the controller-manager, scheduler, and proxy find the apiserver
# KUBE_MASTER="--master=http://127.0.0.1:8080"

kubelet 配置

###
# kubernetes kubelet (minion) config

# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)
KUBELET_ADDRESS="--address=192.168.1.14"

# The port for the info server to serve on
# KUBELET_PORT="--port=10250"

# You may leave this blank to use the actual hostname
KUBELET_HOSTNAME="--hostname-override=docker4.node"

# location of the api-server
# KUBELET_API_SERVER=""

# Add your own!
KUBELET_ARGS="--cgroup-driver=cgroupfs 
              --cluster-dns=10.254.0.2 
              --resolv-conf=/etc/resolv.conf 
              --experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig 
              --kubeconfig=/etc/kubernetes/kubelet.kubeconfig 
              --require-kubeconfig 
              --cert-dir=/etc/kubernetes/ssl 
              --cluster-domain=cluster.local. 
              --hairpin-mode promiscuous-bridge 
              --serialize-image-pulls=false 
              --pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0"

proxy 配置

###
# kubernetes proxy config

# default config should be adequate

# Add your own!
KUBE_PROXY_ARGS="--bind-address=192.168.1.14 
                 --hostname-override=docker4.node 
                 --kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig 
                 --cluster-cidr=10.254.0.0/16"

5.3、创建 ClusterRoleBinding

由于 kubelet 采用了 TLS Bootstrapping,所有根绝 RBAC 控制策略,kubelet 使用的用户 kubelet-bootstrap 是不具备任何访问 API 权限的,这是需要预先在集群内创建 ClusterRoleBinding 授予其 system:node-bootstrapper Role

# 在任意 master 执行即可
kubectl create clusterrolebinding kubelet-bootstrap 
  --clusterrole=system:node-bootstrapper 
  --user=kubelet-bootstrap

5.4、创建 nginx 代理

根据上面描述的 master HA 架构,此时所有 node 应该连接本地的 nginx 代理,然后 nginx 来负载所有 api server;以下为 nginx 代理相关配置

# 创建配置目录
mkdir -p /etc/nginx

# 写入代理配置
cat << EOF >> /etc/nginx/nginx.conf
error_log stderr notice;

worker_processes auto;
events {
  multi_accept on;
  use epoll;
  worker_connections 1024;
}

stream {
    upstream kube_apiserver {
        least_conn;
        server 192.168.1.11:6443;
        server 192.168.1.12:6443;
        server 192.168.1.13:6443;
    }

    server {
        listen        0.0.0.0:6443;
        proxy_pass    kube_apiserver;
        proxy_timeout 10m;
        proxy_connect_timeout 1s;
    }
}
EOF

# 更新权限
chmod +r /etc/nginx/nginx.conf

为了保证 nginx 的可靠性,综合便捷性考虑,node 节点上的 nginx 使用 docker 启动,同时 使用 systemd 来守护, systemd 配置如下

cat << EOF >> /etc/systemd/system/nginx-proxy.service
[Unit]
Description=kubernetes apiserver docker wrapper
Wants=docker.socket
After=docker.service

[Service]
User=root
PermissionsStartOnly=true
ExecStart=/usr/bin/docker run -p 127.0.0.1:6443:6443 \
                              -v /etc/nginx:/etc/nginx \
                              --name nginx-proxy \
                              --net=host \
                              --restart=on-failure:5 \
                              --memory=512M \
                              nginx:1.13.3-alpine
ExecStartPre=-/usr/bin/docker rm -f nginx-proxy
ExecStop=/usr/bin/docker stop nginx-proxy
Restart=always
RestartSec=15s
TimeoutStartSec=30s

[Install]
WantedBy=multi-user.target
EOF

最后启动 nginx,同时可以使用 kubectl 测试 api server 负载情况

systemctl daemon-reload
systemctl start nginx-proxy
systemctl enable nginx-proxy

启动成功后如下

未分类

kubectl 测试联通性如下

未分类

5.5、添加 Node

一起准备就绪以后就可以启动 node 相关组件了

systemctl daemon-reload
systemctl start kubelet
systemctl enable kubelet

由于采用了 TLS Bootstrapping,所以 kubelet 启动后不会立即加入集群,而是进行证书申请,从日志中可以看到如下输出

Jul 19 14:15:31 docker4.node kubelet[18213]: I0719 14:15:31.810914   18213 feature_gate.go:144] feature gates: map[]
Jul 19 14:15:31 docker4.node kubelet[18213]: I0719 14:15:31.811025   18213 bootstrap.go:58] Using bootstrap kubeconfig to generate TLS client cert, key and kubeconfig file

此时只需要在 master 允许其证书申请即可

# 查看 csr
➜  kubernetes kubectl get csr
NAME        AGE       REQUESTOR           CONDITION
csr-l9d25   2m        kubelet-bootstrap   Pending
# 签发证书
➜  kubernetes kubectl certificate approve csr-l9d25
certificatesigningrequest "csr-l9d25" approved
# 查看 node
➜  kubernetes kubectl get node
NAME           STATUS    AGE       VERSION
docker4.node   Ready     26s       v1.6.7

最后再启动 kube-proxy 组件即可

systemctl start kube-proxy
systemctl enable kube-proxy

5.6、Master 开启 Pod 负载

Master 上部署 Node 与单独 Node 部署大致相同,只需要修改 bootstrap.kubeconfig、kube-proxy.kubeconfig 中的 API Server 地址即可

未分类

然后修改 kubelet、proxy 配置启动即可

systemctl daemon-reload
systemctl start kubelet
systemctl enable kubelet
systemctl start kube-proxy
systemctl enable kube-proxy

最后在 master 签发一下相关证书

kubectl certificate approve csr-z090b

整体部署完成后如下

未分类

六、部署 Calico

网路组件这里采用 Calico,Calico 目前部署也相对比较简单,只需要创建一下 yml 文件即可,具体可参考 Calico 官方文档

Cliaco 官方文档要求 kubelet 启动时要配置使用 cni 插件 –network-plugin=cni,同时 kube-proxy 使用 –masquerade-all 启动,所以需要修改所有 kubelet 和 proxy 配置文件增加这两项,以下默认为这两项已经调整完毕,这里不做演示

# 获取相关 Cliaco.yml
wget http://docs.projectcalico.org/v2.3/getting-started/kubernetes/installation/hosted/calico.yaml

# 修改 Etcd 相关配置,以下列出主要修改部分(etcd 证书内容需要被 base64 转码)

sed -i 's@.*etcd_endpoints:.*@  etcd_endpoints: "https://192.168.1.11:2379,https://192.168.1.12:2379,https://192.168.1.13:2379"@gi' calico.yaml

export ETCD_CERT=`cat /etc/etcd/ssl/etcd.pem | base64 | tr -d 'n'`
export ETCD_KEY=`cat /etc/etcd/ssl/etcd-key.pem | base64 | tr -d 'n'`
export ETCD_CA=`cat /etc/etcd/ssl/etcd-root-ca.pem | base64 | tr -d 'n'`

sed -i "s@.*etcd-cert:.*@  etcd-cert: ${ETCD_CERT}@gi" calico.yaml
sed -i "s@.*etcd-key:.*@  etcd-key: ${ETCD_KEY}@gi" calico.yaml
sed -i "s@.*etcd-ca:.*@  etcd-ca: ${ETCD_CA}@gi" calico.yaml

sed -i 's@.*etcd_ca:.*@  etcd_ca: "/calico-secrets/etcd-ca"@gi' calico.yaml
sed -i 's@.*etcd_cert:.*@  etcd_cert: "/calico-secrets/etcd-cert"@gi' calico.yaml
sed -i 's@.*etcd_key:.*@  etcd_key: "/calico-secrets/etcd-key"@gi' calico.yaml

sed -i '[email protected]/[email protected]/18@gi' calico.yaml

执行部署操作,注意,在开启 RBAC 的情况下需要单独创建 ClusterRole 和 ClusterRoleBinding

kubectl create -f calico.yaml
kubectl apply -f http://docs.projectcalico.org/v2.3/getting-started/kubernetes/installation/rbac.yaml

部署完成后如下

未分类

最后测试一下跨主机通讯

# 创建 deployment
cat << EOF >> demo.deploy.yml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: demo-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: demo
    spec:
      containers:
      - name: demo
        image: mritd/demo
        ports:
        - containerPort: 80
EOF
kubectl create -f demo.deploy.yml

exec 到一台主机 pod 内 ping 另一个不同 node 上的 pod 如下

未分类

七、部署 DNS

7.1、DNS 组件部署

DNS 部署目前有两种方式,一种是纯手动,另一种是使用 Addon-manager,目前个人感觉 Addon-manager 有点繁琐,所以以下采取纯手动部署 DNS 组件

DNS 组件相关文件位于 kubernetes addons 目录下,把相关文件下载下来然后稍作修改即可

# 获取文件
mkdir dns && cd dns
wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kubedns-cm.yaml
wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kubedns-sa.yaml
wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kubedns-svc.yaml.sed
wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kubedns-controller.yaml.sed
mv kubedns-controller.yaml.sed kubedns-controller.yaml
mv kubedns-svc.yaml.sed kubedns-svc.yaml
# 修改配置
sed -i 's/$DNS_DOMAIN/cluster.local/gi' kubedns-controller.yaml
sed -i 's/$DNS_SERVER_IP/10.254.0.2/gi' kubedns-svc.yaml
# 创建(我把所有 yml 放到的 dns 目录中)
kubectl create -f ../dns

接下来测试 DNS,测试方法创建两个 deployment 和 svc,通过在 pod 内通过 svc 域名方式访问另一个 deployment 下的 pod,相关测试的 deploy、svc 配置在这里不在展示,基本情况如下图所示

未分类

未分类

7.2、DNS 自动扩容部署

关于 DNS 自动扩容详细可参考https://kubernetes.io/docs/tasks/administer-cluster/dns-horizontal-autoscaling/

以下直接操作

首先获取 Dns horizontal autoscaler 配置文件

wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns-horizontal-autoscaler/dns-horizontal-autoscaler-rbac.yaml
wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns-horizontal-autoscaler/dns-horizontal-autoscaler.yaml

然后直接 kubectl create -f 即可,DNS 自动扩容计算公式为 replicas = max( ceil( cores * 1/coresPerReplica ) , ceil( nodes * 1/nodesPerReplica ) ),如果想调整 DNS 数量(负载因子),只需要调整 ConfigMap 中对应参数即可,具体计算细节参考上面的官方文档

# 编辑 Config Map
kubectl edit cm kube-dns-autoscaler --namespace=kube-system

ubuntu使用kubeadm安装配置k8s(Kubernetes)

安装

翻墙使用root权限执行以下内容或者参考这里

wget https://coding.net/u/scaffrey/p/hosts/git/raw/master/hosts 
cp hosts /etc/hosts

安装

apt-get update && apt-get install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF > /etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y docker.io
apt-get install -y kubelet kubeadm kubectl kubernetes-cni

配置master节点

在master节点执行以下命令

kubeadm init  --pod-network-cidr 10.244.0.0/16

使master node参与工作负载

kubectl taint nodes --all node-role.kubernetes.io/master-

安装网络

kubectl create -f https://github.com/coreos/flannel/raw/master/Documentation/kube-flannel-rbac.yml
kubectl create -f  https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

配置worker节点

执行初始化master节点后打印的最后一行

kubeadm reset
kubeadm join --token 6aa93f.04f7fbce49b7f3bb 222.20.101.106:6443

使用

使用kubectl的run命令创建deployment

kubectl run nginx --image=nginx:1.10.0
kubectl get pods -o wide

使用expose 将端口暴露出来

kubectl expose deployment nginx --port 80 --type LoadBalancer
kubectl get services -o wide

通过scale命令扩展应用

kubectl scale deployments/nginx --replicas=4
kubectl get pods -o wide

创建nginx版本

更新应用镜像,滚动更新应用镜像

kubectl set image deployments/nginx nginx=qihao/nginx

确认更新

kubectl rollout status deployments/nginx 

回滚到之前版本

kubectl rollout undo deployments/nginx 

负载均衡(不停的刷新服务的地址,过段时间会有变化)

kubectl exec -it nginx-2027219757-r1sqm bash
uname -n > /usr/share/nginx/html/index.html 

结束

kubectl delete deployment nginx && kubectl delete service nginx
kubectl get services -o wide
kubectl get pods -o wide
kubectl get nodes

k8s(kubernetes) kube-proxy转发模式及service转发类型介绍

配置方式

kubernetes版本大于或者等于1.2时,外部网络(即非K8S集群内的网络)访问cluster IP的办法是:
修改master的/etc/kubernetes/proxy,把KUBE_PROXY_ARGS=”“改为KUBE_PROXY_ARGS=”–proxy-mode=userspace”
启动kube-proxy服务
在核心路由设备或者源主机上添加一条路由,访问cluster IP段的路由指向到master上。

kubernetes版本小于1.2时,直接添加路由

kube-proxy转发的两种模式

kube-proxy在转发时主要有两种模式Userspace和Iptables。如下图,左侧是Userspace模式,也是kube-proxy默认的方式,所有的转发都是通过kube-proxy软件实现的;右侧是Iptables模式,所有转发都是通过Iptables内核模块实现,而kube-proxy只负责生成相应的Iptables规则。

未分类

使用Userspace模式(k8s版本为1.2之前默认模式),外部网络可以直接访问cluster IP。
使用Iptables模式(k8s版本为1.2之后默认模式),外部网络不能直接访问cluster IP。
从效率上看,Iptables会更高一些,但是需要Iptables version >=1.4.11,Iptables模式在k8s1.2版本放出。

service转发后端服务的三种类型

ClusterIP:提供一个集群内部的虚拟IP以供Pod访问。
NodePort:在每个Node上打开一个端口以供外部访问。
LoadBalancer:通过外部的负载均衡器来访问。

ClusterIP

此类型会提供一个集群内部的虚拟IP(与Pod不在同一网段),以供集群内部的pod之间通信使用。ClusterIP也是Kubernetes service的默认类型。

未分类

为了实现图上的功能主要需要以下几个组件的协同工作:
apiserver:在创建service时,apiserver接收到请求以后将数据存储到etcd中。
kube-proxy:k8s的每个节点中都有该进程,负责实现service功能,这个进程负责感知service,pod的变化,并将变化的信息写入本地的iptables中。
iptables:使用NAT等技术将virtualIP的流量转至endpoint中。

NodePort

NodePort模式除了使用cluster ip外,也将service的port映射到每个node的一个指定内部port上,映射的每个node的内部port都一样。
为每个节点暴露一个端口,通过nodeip + nodeport可以访问这个服务,同时服务依然会有cluster类型的ip+port。内部通过clusterip方式访问,外部通过nodeport方式访问。

loadbalance

只能使用在在gce这样的云环境上。

service的三种端口

port

service暴露在cluster ip上的端口,:port 是提供给集群内部客户访问service的入口。

nodePort

nodePort是k8s提供给集群外部客户访问service入口的一种方式,:nodePort 是提供给集群外部客户访问service的入口。

targetPort
targetPort是pod上的端口,从port和nodePort上到来的数据最终经过kube-proxy流入到后端pod的targetPort上进入容器。

port、nodePort总结

总的来说,port和nodePort都是service的端口,前者暴露给集群内客户访问服务,后者暴露给集群外客户访问服务。从这两个端口到来的数据都需要经过反向代理kube-proxy流入后端pod的targetPod,从而到达pod上的容器内。

k8s(kubernetes)部署三个节点的redis cluster

目的

redis clustor 需要6台服务器才能正常运⾏,由于种种原因,开发或者某些特别的需求,只能在3台服务器上运⾏redis clustor。在不使用哨兵模式情况下,而使⽤最新的clustor模式运行redis。

本文仅作为redis部署方式的研究及理解

准备工作

制作redis docker.latest镜像其中包含以下组件:

  1. redis-cli
  2. ruby
  3. redis-trib

打包到镜像上传到阿里镜像服务器中cluster-redis:latest

创建集群操作

3台服务器上各自运行两个redis容器

使用以下编写好的redis-cluster部署文件,可在一台部署出两个不同端口,不同角⾊的redis容器。

redis-cluster.yaml

apiVersion: extensions/v1beta1
kind: Deployment

metadata:

name: redis-blue

labels:

app: redis

member: redis-blue

spec:

replicas: 3

template:

metadata:

labels:

app: redis

member: redis-blue

spec:

hostNetwork: true

containers:

- name: redis

image: registry.cn-hangzhou.aliyuncs.com/wise2c/cluster-redis:latest

command: ["/bin/sh", "-c"]

args: ["echo 'dir /tmp/data' >> /root/redis.conf && /usr/local/bin/redis-server /root/redis.conf"]

- /usr/local/bin/redis-server

- /root/redis.conf

ports:

- name: redis-port

containerPort: 6379

- name: cluster-port

containerPort: 16379

volumeMounts:

- mountPath: /tmp/data

name: data

volumes:

- name: data

hostPath:

path: /tmp/redis-blue

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

name: redis-green

labels:

app: redis

member: redis-green

spec:

replicas: 3

template:

metadata:

labels:

app: redis

member: redis-green

spec:

hostNetwork: true

containers:

- name: redis

image: registry.cn-hangzhou.aliyuncs.com/wise2c/cluster-redis:latest

command: ["/bin/sh", "-c"]

args: ["sed -i 's/6379/6380/g' /root/redis.conf && echo 'dir /tmp/data' >> /root/redis.conf && /usr/local/ports:

- name: redis-port

containerPort: 6380

- name: cluster-port

containerPort: 16380

volumeMounts:

- mountPath: /tmp/data

name: data

volumes:

- name: data

hostPath:

path: /tmp/redis-green

kubectl create -f redis-cluster.yaml

执行以下脚本,创建出整个redis集群。redis会自动分配哈希槽,使得master与其对应的slave不会出现在同一台服务器上。

create_cluster.sh

!/usr/bin/env bash

redis_count=`kubectl get pod -o wide -l app=redis | grep Running | wc -l`
echo "redis_count:"$redis_count

if [ $redis_count -ne 6 ]; then
echo "the running redis count: ${redis_count} is error"
exit 1
fi
redis_blue_ips=`kubectl get pod -o wide -l app=redis -l member=redis-blue | awk 'NR>1{printf $6":6379 "}'
redis_green_ips=`kubectl get pod -o wide -l app=redis -l member=redis-green | awk 'NR>1{printf $6":6380 "}'
redis_ips=$redis_blue_ips" "$redis_green_ips
echo "redis_ips:"$redis_ips
redis_blue_name=`kubectl get pod -o wide -l app=redis -l member=redis-blue | grep Running | awk '{printf $1" "}'
echo $redis_ips | awk -F' ' '{for( i=1;i<NF; i++ ) print $i}' 
kubectl create -f redis-cluster.yaml

bash create_cluster.sh

关掉其中一台机器

由于master和slave 不在同一台机器上,当我们直接关掉其中⼀一台vm,比如vm2

这时vm3上,由redis cluster自动恢复vm3slave2(6380) —> master2(6380) 提升为master,集群工作正常,业务不中断。

恢复关掉的的机器

当我们重新启动vm2, 这时候集群的状态:

这时集群工作正常,此时vm3有2个master。如果我们关掉vm3,会让集群中2个master同时下线,导致集群无法自动恢复。

重点:执行以下脚本,提升slave2到master2,恢复集群的自动修复功能。

failover.sh

!/usr/bin/env bash

redis_blue_name=`kubectl get pod -o wide -l app=redis -l member=redis-blue | grep Running | awk '{printf $1":6379 redis_names=`kubectl get pod -o wide -l app=redis -l member=redis-blue | grep Running | awk '{printf $1","}'
redis_array=${redis_names//,/ }
for redis_name in $redis_array
do
kubectl exec -it ${redis_name} -- redis-cli cluster failover
done
bash failover.sh

集群自动恢复,变成下面的状态。

集群工作正常,业务不中断。

作者后话

以上的操作,目的是让各台虚拟上不出现有2个master。当其中一台虚拟机出现当机,集群就不能正常工作.。如果是3台master和3台slave分别在不同的机器刚好错开,redis cluster能自动恢复.。最好的解决方案,依然是有6台虚拟机、3台master、3台slave的redis cluster。

To do

我们可以进⼀一步将

1.create-redis.sh
2.failover.sh
3.kubectl

制作为镜像, k8s deployment运行此镜像。实现:

1.创建集群
2.恢复集群的自动修复功能(任意关掉其中一台服务器,业务不中断。每过一秒,检查集群中是否有slave需要提升权限,集群都能正常工作.业务不中断。)