Kubernetes集群中Service的滚动更新

在移动互联网时代,消费者的消费行为已经“全天候化”,为此,商家的业务系统也要保持7×24小时不间断地提供服务以满足消费者的需求。很难想像如今还会有以“中断业务”为前提的服务系统更新升级。如果微信官方发布公告说:每周六晚23:00~次日凌晨2:00进行例行系统升级,不能提供服务,作为用户的你会怎么想、怎么做呢?因此,各个平台在最初设计时就要考虑到服务的更新升级问题,部署在Kubernetes集群中的Service也不例外。

一、预备知识

1、滚动更新Rolling-update

传统的升级更新,是先将服务全部下线,业务停止后再更新版本和配置,然后重新启动并提供服务。这样的模式已经完全不能满足“时代的需要”了。在并发化、高可用系统普及的今天,服务的升级更新至少要做到“业务不中断”。而滚动更新(Rolling-update)恰是满足这一需求的一种系统更新升级方案。

简单来说,滚动更新就是针对多实例服务的一种不中断服务的更新升级方式。一般情况,对于多实例服务,滚动更新采用对各个实例逐个进行单独更新而非同一时刻对所有实例进行全部更新的方式。“滚动更新”的先进之处在于“滚动”这个概念的引入,笔者觉得它至少有以下两点含义:

a) “滚动”给人一种“圆”的映像,表意:持续,不中断。“滚动”的理念是一种趋势,我们常见的“滚动发布”、“持续交付”都是“滚动”理念的应用。与传统的大版本周期性发布/更新相比,”滚动”可以让用户更快、更及时地使用上新Feature,缩短市场反馈周期,同时滚动式的发布和更新又会将对用户体验的影响降到最小化。

b) “滚动”可向前,也可向后。我们可以在更新过程中及时发现“更新”存在的问题,并“向后滚动”,实现更新的回退,可以最大程度上降低每次更新升级的风险。

对于在Kubernetes集群部署的Service来说,Rolling update就是指一次仅更新一个Pod,并逐个进行更新,而不是在同一时刻将该Service下面的所有Pod shutdown,避免将业务中断的尴尬。

2、Service、Deployment、Replica Set、Replication Controllers和Pod之间的关系

对于我们要部署的Application来说,一般是由多个抽象的Service组成。在Kubernetes中,一个Service通过label selector match出一个Pods集合,这些Pods作为Service的endpoint,是真正承载业务的实体。而Pod在集群内的部署、调度、副本数保持则是通过Deployment或ReplicationControllers这些高level的抽象来管理的,下面是一幅示意图:

未分类

新版本的Kubernetes推荐用Deployment替代ReplicationController,在Deployment这个概念下在保持Pod副本数上实际发挥作用的是隐藏在背后的Replica Set。

因此,我们可以看到Kubernetes上Service的rolling update实质上是对Service所match出来的Pod集合的Rolling update,而控制Pod部署、调度和副本调度的却又恰恰是Deployment和replication controller,因此后两者才是kubernetes service rolling update真正要面对的实体。

二、kubectl rolling-update子命令

kubernetes在kubectl cli工具中仅提供了对Replication Controller的rolling-update支持,通过kubectl -help,我们可以查看到下面的命令usage描述:

# kubectl -help
... ...
Deploy Commands:
  rollout        Manage a deployment rollout
  rolling-update Perform a rolling update of the given ReplicationController
  scale          Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job
  autoscale      Auto-scale a Deployment, ReplicaSet, or ReplicationController
... ...

# kubectl help rolling-update
... ...
Usage:
  kubectl rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] --image=NEW_CONTAINER_IMAGE | -f
NEW_CONTROLLER_SPEC) [options]
... ...

我们现在来看一个例子,看一下kubectl rolling-update是如何对service下的Pods进行滚动更新的。我们的kubernetes集群有两个版本的Nginx:

# docker images|grep nginx
nginx                                                    1.11.9                     cc1b61406712        2 weeks ago         181.8 MB
nginx                                                    1.10.1                     bf2b4c2d7bf5        4 months ago        180.7 MB

在例子中我们将Service的Pod从nginx 1.10.1版本滚动升级到1.11.9版本。

我们的rc-demo-v0.1.yaml文件内容如下:

apiVersion: v1
kind: ReplicationController
metadata:
  name: rc-demo-nginx-v0.1
spec:
  replicas: 4
  selector:
    app: rc-demo-nginx
    ver: v0.1
  template:
    metadata:
      labels:
        app: rc-demo-nginx
        ver: v0.1
    spec:
      containers:
        - name: rc-demo-nginx
          image: nginx:1.10.1
          ports:
            - containerPort: 80
              protocol: TCP
          env:
            - name: RC_DEMO_VER
              value: v0.1

创建这个replication controller:

# kubectl create -f rc-demo-v0.1.yaml
replicationcontroller "rc-demo-nginx-v0.1" created

# kubectl get pods -o wide
NAME                       READY     STATUS    RESTARTS   AGE       IP             NODE
rc-demo-nginx-v0.1-2p7v0   1/1       Running   0          1m        172.30.192.9   iz2ze39jeyizepdxhwqci6z
rc-demo-nginx-v0.1-9pk3t   1/1       Running   0          1m        172.30.192.8   iz2ze39jeyizepdxhwqci6z
rc-demo-nginx-v0.1-hm6b9   1/1       Running   0          1m        172.30.0.9     iz25beglnhtz
rc-demo-nginx-v0.1-vbxpl   1/1       Running   0          1m        172.30.0.10    iz25beglnhtz

Service manifest文件rc-demo-svc.yaml的内容如下:

apiVersion: v1
kind: Service
metadata:
  name: rc-demo-svc
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    app: rc-demo-nginx

创建这个service:

# kubectl create -f rc-demo-svc.yaml
service "rc-demo-svc" created

# kubectl describe svc/rc-demo-svc
Name:            rc-demo-svc
Namespace:        default
Labels:            <none>
Selector:        app=rc-demo-nginx
Type:            ClusterIP
IP:            10.96.172.246
Port:            <unset>    80/TCP
Endpoints:        172.30.0.10:80,172.30.0.9:80,172.30.192.8:80 + 1 more...
Session Affinity:    None
No events.

可以看到之前replication controller创建的4个Pod都被置于rc-demo-svc这个service的下面了,我们来访问一下该服务:

# curl -I http://10.96.172.246:80
HTTP/1.1 200 OK
Server: nginx/1.10.1
Date: Wed, 08 Feb 2017 08:45:19 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 31 May 2016 14:17:02 GMT
Connection: keep-alive
ETag: "574d9cde-264"
Accept-Ranges: bytes

# kubectl exec rc-demo-nginx-v0.1-2p7v0  env
... ...
RC_DEMO_VER=v0.1
... ...

通过Response Header中的Server字段,我们可以看到当前Service pods中的nginx版本为1.10.1;通过打印Pod中环境变量,得到RC_DEMO_VER=v0.1。

接下来,我们来rolling-update rc-demo-nginx-v0.1这个rc,我们的新rc manifest文件rc-demo-v0.2.yaml内容如下:

apiVersion: v1
kind: ReplicationController
metadata:
  name: rc-demo-nginx-v0.2
spec:
  replicas: 4
  selector:
    app: rc-demo-nginx
    ver: v0.2
  template:
    metadata:
      labels:
        app: rc-demo-nginx
        ver: v0.2
    spec:
      containers:
        - name: rc-demo-nginx
          image: nginx:1.11.9
          ports:
            - containerPort: 80
              protocol: TCP
          env:
            - name: RC_DEMO_VER
              value: v0.2

rc-demo-new.yaml与rc-demo-old.yaml有几点不同:rc的name、image的版本以及RC_DEMO_VER这个环境变量的值:

# diff rc-demo-v0.2.yaml rc-demo-v0.1.yaml
4c4
<   name: rc-demo-nginx-v0.2
---
>   name: rc-demo-nginx-v0.1
9c9
<     ver: v0.2
---
>     ver: v0.1
14c14
<         ver: v0.2
---
>         ver: v0.1
18c18
<           image: nginx:1.11.9
---
>           image: nginx:1.10.1
24c24
<               value: v0.2
---
>               value: v0.1

我们开始rolling-update,为了便于跟踪update过程,这里将update-period设为10s,即每隔10s更新一个Pod:

#  kubectl rolling-update rc-demo-nginx-v0.1 --update-period=10s -f rc-demo-v0.2.yaml
Created rc-demo-nginx-v0.2
Scaling up rc-demo-nginx-v0.2 from 0 to 4, scaling down rc-demo-nginx-v0.1 from 4 to 0 (keep 4 pods available, don't exceed 5 pods)
Scaling rc-demo-nginx-v0.2 up to 1
Scaling rc-demo-nginx-v0.1 down to 3
Scaling rc-demo-nginx-v0.2 up to 2
Scaling rc-demo-nginx-v0.1 down to 2
Scaling rc-demo-nginx-v0.2 up to 3
Scaling rc-demo-nginx-v0.1 down to 1
Scaling rc-demo-nginx-v0.2 up to 4
Scaling rc-demo-nginx-v0.1 down to 0
Update succeeded. Deleting rc-demo-nginx-v0.1
replicationcontroller "rc-demo-nginx-v0.1" rolling updated to "rc-demo-nginx-v0.2"

从日志可以看出:kubectl rolling-update逐渐增加 rc-demo-nginx-v0.2的scale并同时逐渐减小 rc-demo-nginx-v0.1的scale值直至减到0。

在升级过程中,我们不断访问rc-demo-svc,可以看到新旧Pod版本共存的状态,服务并未中断:

# curl -I http://10.96.172.246:80
HTTP/1.1 200 OK
Server: nginx/1.10.1
... ...

# curl -I http://10.96.172.246:80
HTTP/1.1 200 OK
Server: nginx/1.11.9
... ...

# curl -I http://10.96.172.246:80
HTTP/1.1 200 OK
Server: nginx/1.10.1
... ...

更新后的一些状态信息:

# kubectl get rc
NAME                 DESIRED   CURRENT   READY     AGE
rc-demo-nginx-v0.2   4         4         4         5m

# kubectl get pods
NAME                       READY     STATUS    RESTARTS   AGE
rc-demo-nginx-v0.2-25b15   1/1       Running   0          5m
rc-demo-nginx-v0.2-3jlpk   1/1       Running   0          5m
rc-demo-nginx-v0.2-lcnf9   1/1       Running   0          6m
rc-demo-nginx-v0.2-s7pkc   1/1       Running   0          5m

# kubectl exec rc-demo-nginx-v0.2-25b15  env
... ...
RC_DEMO_VER=v0.2
... ...

官方文档说kubectl rolling-update是由client side实现的rolling-update,这是因为roll-update的逻辑都是由kubectl发出N条命令到APIServer完成的,在kubectl的代码中我们可以看到这点:

//https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/rollingupdate.go
... ...
func RunRollingUpdate(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error {
    ... ...
    err = updater.Update(config)
    if err != nil {
        return err
    }
    ... ...
}

//https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/rolling_updater.go
func (r *RollingUpdater) Update(config *RollingUpdaterConfig) error {
    ... ...
    // Scale newRc and oldRc until newRc has the desired number of replicas and
    // oldRc has 0 replicas.
    progressDeadline := time.Now().UnixNano() + config.Timeout.Nanoseconds()
    for newRc.Spec.Replicas != desired || oldRc.Spec.Replicas != 0 {
        // Store the existing replica counts for progress timeout tracking.
        newReplicas := newRc.Spec.Replicas
        oldReplicas := oldRc.Spec.Replicas

        // Scale up as much as possible.
        scaledRc, err := r.scaleUp(newRc, oldRc, desired, maxSurge, maxUnavailable, scaleRetryParams, config)
        if err != nil {
            return err
        }
        newRc = scaledRc
    ... ...
}

在rolling_updater.go中Update方法使用一个for循环完成了逐步减少old rc的replicas和增加new rc的replicas的工作,直到new rc到达期望值,old rc的replicas变为0。

通过kubectl rolling-update实现的滚动更新有很多不足:

  • 由kubectl实现,很可能因为网络原因导致update中断;
  • 需要创建一个新的rc,名字与要更新的rc不能一样;虽然这个问题不大,但实施起来也蛮别扭的;
  • 回滚还需要执行rolling-update,只是用的老版本的rc manifest文件;
  • service执行的rolling-update在集群中没有记录,后续无法跟踪rolling-update历史。

不过,由于Replication Controller已被Deployment这个抽象概念所逐渐代替,下面我们来考虑如何实现Deployment的滚动更新以及deployment滚动更新的优势。

三、Deployment的rolling-update

kubernetes Deployment是一个更高级别的抽象,就像文章开头那幅示意图那样,Deployment会创建一个Replica Set,用来保证Deployment中Pod的副本数。由于kubectl rolling-update仅支持replication controllers,因此要想rolling-updata deployment中的Pod,你需要修改Deployment自己的manifest文件并应用。这个修改会创建一个新的Replica Set,在scale up这个Replica Set的Pod数的同时,减少原先的Replica Set的Pod数,直至zero。而这一切都发生在Server端,并不需要kubectl参与。

我们同样来看一个例子。我们建立第一个版本的deployment manifest文件:deployment-demo-v0.1.yaml。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deployment-demo
spec:
  replicas: 4
  selector:
    matchLabels:
      app: deployment-demo-nginx
  minReadySeconds: 10
  template:
    metadata:
      labels:
        app: deployment-demo-nginx
        version: v0.1
    spec:
      containers:
        - name: deployment-demo
          image: nginx:1.10.1
          ports:
            - containerPort: 80
              protocol: TCP
          env:
            - name: DEPLOYMENT_DEMO_VER
              value: v0.1

创建该deployment:

# kubectl create -f deployment-demo-v0.1.yaml --record
deployment "deployment-demo" created

# kubectl get deployments
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment-demo   4         4         4            0           10s

# kubectl get rs
NAME                         DESIRED   CURRENT   READY     AGE
deployment-demo-1818355944   4         4         4         13s

# kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP             NODE
deployment-demo-1818355944-78spp   1/1       Running   0          24s       172.30.0.10    iz25beglnhtz
deployment-demo-1818355944-7wvxk   1/1       Running   0          24s       172.30.0.9     iz25beglnhtz
deployment-demo-1818355944-hb8tt   1/1       Running   0          24s       172.30.192.9   iz2ze39jeyizepdxhwqci6z
deployment-demo-1818355944-jtxs2   1/1       Running   0          24s       172.30.192.8   iz2ze39jeyizepdxhwqci6z

# kubectl exec deployment-demo-1818355944-78spp env
... ...
DEPLOYMENT_DEMO_VER=v0.1
... ...

deployment-demo创建了ReplicaSet:deployment-demo-1818355944,用于保证Pod的副本数。

我们再来创建使用了该deployment中Pods的Service:

# kubectl create -f deployment-demo-svc.yaml
service "deployment-demo-svc" created

# kubectl get service
NAME                  CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
deployment-demo-svc   10.109.173.225   <none>        80/TCP    5s
kubernetes            10.96.0.1        <none>        443/TCP   42d

# kubectl describe service/deployment-demo-svc
Name:            deployment-demo-svc
Namespace:        default
Labels:            <none>
Selector:        app=deployment-demo-nginx
Type:            ClusterIP
IP:            10.109.173.225
Port:            <unset>    80/TCP
Endpoints:        172.30.0.10:80,172.30.0.9:80,172.30.192.8:80 + 1 more...
Session Affinity:    None
No events.

# curl -I http://10.109.173.225:80
HTTP/1.1 200 OK
Server: nginx/1.10.1
... ...

好了,我们看到该service下有四个pods,Service提供的服务也运行正常。

接下来,我们对该Service进行更新。为了方便说明,我们建立了deployment-demo-v0.2.yaml文件,其实你也大可不必另创建文件,直接再上面的deployment-demo-v0.1.yaml文件中修改也行:

# diff deployment-demo-v0.2.yaml deployment-demo-v0.1.yaml
15c15
<         version: v0.2
---
>         version: v0.1
19c19
<           image: nginx:1.11.9
---
>           image: nginx:1.10.1
25c25
<               value: v0.2
---
>               value: v0.1

我们用deployment-demo-v0.2.yaml文件来更新之前创建的deployments中的Pods:

# kubectl apply -f deployment-demo-v0.2.yaml --record
deployment "deployment-demo" configured

apply命令是瞬间接收到apiserver返回的Response并结束的。但deployment的rolling-update过程还在进行:

# kubectl describe deployment deployment-demo
Name:            deployment-demo
... ...
Replicas:        2 updated | 4 total | 3 available | 2 unavailable
StrategyType:        RollingUpdate
MinReadySeconds:    10
RollingUpdateStrategy:    1 max unavailable, 1 max surge
Conditions:
  Type        Status    Reason
  ----        ------    ------
  Available     True    MinimumReplicasAvailable
OldReplicaSets:    deployment-demo-1818355944 (3/3 replicas created)
NewReplicaSet:    deployment-demo-2775967987 (2/2 replicas created)
Events:
  FirstSeen    LastSeen    Count    From                SubObjectPath    Type        Reason            Message
  ---------    --------    -----    ----                -------------    --------    ------            -------
  12m        12m        1    {deployment-controller }            Normal        ScalingReplicaSet    Scaled up replica set deployment-demo-1818355944 to 4
  11s        11s        1    {deployment-controller }            Normal        ScalingReplicaSet    Scaled up replica set deployment-demo-2775967987 to 1
  11s        11s        1    {deployment-controller }            Normal        ScalingReplicaSet    Scaled down replica set deployment-demo-1818355944 to 3
  11s        11s        1    {deployment-controller }            Normal        ScalingReplicaSet    Scaled up replica set deployment-demo-2775967987 to 2

# kubectl get pods
NAME                               READY     STATUS              RESTARTS   AGE
deployment-demo-1818355944-78spp   1/1       Terminating         0          12m
deployment-demo-1818355944-hb8tt   1/1       Terminating         0          12m
deployment-demo-1818355944-jtxs2   1/1       Running             0          12m
deployment-demo-2775967987-5s9qx   0/1       ContainerCreating   0          0s
deployment-demo-2775967987-lf5gw   1/1       Running             0          12s
deployment-demo-2775967987-lxbx8   1/1       Running             0          12s
deployment-demo-2775967987-pr0hl   0/1       ContainerCreating   0          0s

# kubectl get rs
NAME                         DESIRED   CURRENT   READY     AGE
deployment-demo-1818355944   1         1         1         12m
deployment-demo-2775967987   4         4         4         17s

我们可以看到这个update过程中ReplicaSet的变化,同时这个过程中服务并未中断,只是新旧版本短暂地交错提供服务:

# curl -I http://10.109.173.225:80
HTTP/1.1 200 OK
Server: nginx/1.11.9
... ...

# curl -I http://10.109.173.225:80
HTTP/1.1 200 OK
Server: nginx/1.10.1
... ...

# curl -I http://10.109.173.225:80
HTTP/1.1 200 OK
Server: nginx/1.10.1
... ...

最终所有Pod被替换为了v0.2版本:

kubectl exec deployment-demo-2775967987-5s9qx env
... ...
DEPLOYMENT_DEMO_VER=v0.2
... ...

# curl -I http://10.109.173.225:80
HTTP/1.1 200 OK
Server: nginx/1.11.9
... ...

我们发现deployment的create和apply命令都带有一个–record参数,这是告诉apiserver记录update的历史。通过kubectl rollout history可以查看deployment的update history:

#  kubectl rollout history deployment deployment-demo
deployments "deployment-demo"
REVISION    CHANGE-CAUSE
1        kubectl create -f deployment-demo-v0.1.yaml --record
2        kubectl apply -f deployment-demo-v0.2.yaml --record

如果没有加“–record”,那么你得到的历史将会类似这样的结果:

#  kubectl rollout history deployment deployment-demo
deployments "deployment-demo"
REVISION    CHANGE-CAUSE
1        <none>

同时,我们会看到old ReplicaSet并未被删除:

# kubectl get rs
NAME                         DESIRED   CURRENT   READY     AGE
deployment-demo-1818355944   0         0         0         25m
deployment-demo-2775967987   4         4         4         13m

这些信息都存储在server端,方便回退!

Deployment下Pod的回退操作异常简单,通过rollout undo即可完成。rollout undo会将Deployment回退到record中的上一个revision(见上面rollout history的输出中有revision列):

# kubectl rollout undo deployment deployment-demo
deployment "deployment-demo" rolled back

rs的状态又颠倒回来:

# kubectl get rs
NAME                         DESIRED   CURRENT   READY     AGE
deployment-demo-1818355944   4         4         4         28m
deployment-demo-2775967987   0         0         0         15m

查看update历史:

# kubectl rollout history deployment deployment-demo
deployments "deployment-demo"
REVISION    CHANGE-CAUSE
2        kubectl apply -f deployment-demo-v0.2.yaml --record
3        kubectl create -f deployment-demo-v0.1.yaml --record

可以看到history中最多保存了两个revision记录(这个Revision保存的数量应该可以设置)。

四、通过API实现的deployment rolling-update

我们的最终目标是通过API来实现service的rolling-update。Kubernetes提供了针对deployment的Restful API,包括:create、read、replace、delete、patch、rollback等。从这些API的字面意义上看,patch和rollback很可能符合我们的需要,我们需要验证一下。

我们将deployment置为v0.1版本,即:image: nginx:1.10.1,DEPLOYMENT_DEMO_VER=v0.1。然后我们尝试通过patch API将deployment升级为v0.2版本,由于patch API仅接收json格式的body内容,我们将 deployment-demo-v0.2.yaml转换为json格式:deployment-demo-v0.2.json。patch是局部更新,这里偷个懒儿,直接将全部deployment manifest内容发给了APIServer,让server自己做merge^0^。

执行下面curl命令:

# curl -H 'Content-Type:application/strategic-merge-patch+json' -X PATCH --data @deployment-demo-v0.2.json http://localhost:8080/apis/extensions/v1beta1/namespaces/default/deployments/deployment-demo

这个命令输出一个merge后的Deployment json文件,由于内容太多,这里就不贴出来了,内容参见:patch-api-output.txt。

跟踪命令执行时的deployment状态,我们可以看到该命令生效了:新旧两个rs的Scale值在此消彼长,两个版本的Pod在交替提供服务。

# kubectl get rs
NAME                         DESIRED   CURRENT   READY     AGE
deployment-demo-1818355944   3         3         3         12h
deployment-demo-2775967987   2         2         2         12h

# curl  -I http://10.109.173.225:80
HTTP/1.1 200 OK
Server: nginx/1.10.1
... ...

# curl  -I http://10.109.173.225:80
HTTP/1.1 200 OK
Server: nginx/1.11.9
... ...

# curl  -I http://10.109.173.225:80
HTTP/1.1 200 OK
Server: nginx/1.10.1
... ...

不过通过这种方式update后,通过rollout history查看到的历史就有些“不那么精确了”:

#kubectl rollout history deployment deployment-demo
deployments "deployment-demo"
REVISION    CHANGE-CAUSE
8       kubectl create -f deployment-demo-v0.1.yaml --record
9        kubectl create -f deployment-demo-v0.1.yaml --record

目前尚无好的方法。但rolling update的确是ok了。

Patch API支持三种类型的Content-type:json-patch+json、strategic-merge-patch+json和merge-patch+json。对于后面两种,从测试效果来看,都一样。但json-patch+json这种类型在测试的时候一直报错:

# curl -H 'Content-Type:application/json-patch+json' -X PATCH --data @deployment-demo-v0.2.json http://localhost:8080/apis/extensions/v1beta1/namespaces/default/deployments/deployment-demo
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "json: cannot unmarshal object into Go value of type jsonpatch.Patch",
  "code": 500
}

kubectl patch子命令似乎使用的是strategic-merge-patch+json。源码中也没有过多说明三种方式的差别:

//pkg/kubectl/cmd/patch.go
func getPatchedJSON(patchType api.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) {
    switch patchType {
    case api.JSONPatchType:
        patchObj, err := jsonpatch.DecodePatch(patchJS)
        if err != nil {
            return nil, err
        }
        return patchObj.Apply(originalJS)

    case api.MergePatchType:
        return jsonpatch.MergePatch(originalJS, patchJS)

    case api.StrategicMergePatchType:
        return strategicpatch.StrategicMergePatchData(originalJS, patchJS, obj)

    default:
        // only here as a safety net - go-restful filters content-type
        return nil, fmt.Errorf("unknown Content-Type header for patch: %v", patchType)
    }
}

// DecodePatch decodes the passed JSON document as an RFC 6902 patch.

// MergePatch merges the patchData into the docData.

// StrategicMergePatch applies a strategic merge patch. The patch and the original document
// must be json encoded content. A patch can be created from an original and a modified document
// by calling CreateStrategicMergePatch.

接下来,我们使用deployment rollback API实现deployment的rollback。我们创建一个deployment-demo-rollback.json文件作为请求的内容:

//deployment-demo-rollback.json
{
        "name" : "deployment-demo",
        "rollbackTo" : {
                "revision" : 0
        }
}

revision:0 表示回退到上一个revision。执行下面命令实现rollback:

# curl -H 'Content-Type:application/json' -X POST --data @deployment-demo-rollback.json http://localhost:8080/apis/extensions/v1beta1/namespaces/default/deployments/deployment-demo/rollback
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "rollback request for deployment "deployment-demo" succeeded",
  "code": 200
}

# kubectl describe deployment/deployment-demo
... ...
Events:
  FirstSeen    LastSeen    Count    From                SubObjectPath    Type        Reason            Message
  ---------    --------    -----    ----                -------------    --------    ------            -------
... ...
 27s        27s        1    {deployment-controller }            Normal        DeploymentRollback    Rolled back deployment "deployment-demo" to revision 1
... ...

通过查看deployment状态可以看出rollback成功了。但这个API的response似乎有些bug,明明是succeeded了(code:200),但status却是”Failure”。

如果你在patch或rollback过程中还遇到什么其他问题,可以通过kubectl describe deployment/deployment-demo 查看输出的Events中是否有异常提示。

五、小结

从上面的实验来看,通过Kubernetes提供的API是可以实现Service中Pods的rolling-update的,但这更适用于无状态的Service。对于那些有状态的Service(通过PetSet或是1.5版本后的Stateful Set实现的),这么做是否还能满足要求还不能确定。由于暂时没有环境,这方面尚未测试。

上述各个manifest的源码可以在这里下载到。

在 Kubernetes 集群中运行 WordPress

作为一名开发者,我会尝试留意那些我可能不会每天使用的技术的进步。了解这些技术至关重要,因为它们可能会间接影响到我的工作。比如由 Docker 推动的、近期正在兴起的容器化技术,可用于上规模地托管 Web 应用。从技术层面来讲,我并不是一个 DevOps,但当我每天构建 Web 应用时,多去留意这些技术如何去发展,会对我有所裨益。

这种进步的一个绝佳的例子,是近一段时间高速发展的容器编排平台。它允许你轻松地部署、管理容器化应用,并对它们的规模进行调整。目前看来,容器编排的流行工具有 Kubernetes (来自 Google),Docker Swarm 和 Apache Mesos。如果你想较好的了解上面那些技术以及它们的区别,我推荐你看一下这篇文章。

在这篇文章中,我们将会从一些简单的操作开始,了解一下 Kubernetes 平台,看看如何将一个 WordPress 网站部署在本地机器上的一个单节点集群中。

安装 Kubernetes

在 Kubernetes 文档中有一个很好的互动教程,涵盖了很多东西。但出于本文的目的,我只会介绍在 MacOS 中 Kuberentes 的安装和使用。

我们要做的第一件事是在你的本地主机中安装 Kubernetes。我们将使用一个叫做 MiniKube 的工具,它专门用于在你的机器上方便地设置一个用于测试的 Kubernetes 集群。

根据 Minikube 文档,在我们开始之前,有一些先决条件。首先要保证你已经安装了一个 Hypervisor (我将会使用 Virtualbox)。接下来,我们需要安装 Kubernetes 命令行工具(也就是 kubectl)。如果你在用 Homebrew,这一步非常简单,只需要运行命令:

$ brew install kubectl

现在我们可以真正 安装 Minikube 了:

$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.21.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

最后,我们要启动 Minicube 创建一个虚拟机,来作为我们的单节点 Kubernetes 集群。现在我要说一点:尽管我们在本文中只在本地运行它,但是在真正的服务器上运行 Kubernetes 集群时,后面提到的大多数概念都会适用。在多节点集群上,“主节点”将负责管理其它工作节点(虚拟机或物理服务器),并且 Kubernetes 将会在集群中自动进行容器的分发和调度。

$ minikube start --vm-driver=virtualbox

安装 Helm

现在,本机中应该有一个正在运行的(单节点)Kubernetes 集群了。我们现在可以用任何方式来与 Kubernetes 交互。如果你想现在可以体验一下,我觉得 kubernetesbyexample.com 可以很好地向你介绍 Kubernetes 的概念和术语。

虽然我们可以手动配置这些东西,但实际上我们将会使用另外的工具,来将我们的 WordPress 应用部署到 Kubernetes 集群中。Helm 被称为“Kubernetes 的包管理工具”,它可以让你轻松地在你的集群中部署预构建的软件包,也就是“图表chart”。你可以把图表看做一组专为特定应用(如 WordPress)而设计的容器定义和配置。首先我们在本地主机上安装 Helm:

$ brew install kubernetes-helm

然后我们需要在集群中安装 Helm。 幸运的是,只需要运行下面的命令就好:

$ helm init

安装 WordPress

现在 Helm 已经在我们的集群中运行了,我们可以安装 WordPress 图表。运行:

$ helm install --namespace wordpress --name wordpress --set serviceType=NodePort stable/wordpress  

这条命令将会在容器中安装并运行 WordPress,并在容器中运行 MariaDB 作为数据库。它在 Kubernetes 中被称为“Pod”。一个 Pod 基本上可视为一个或多个应用程序容器和这些容器的一些共享资源(例如存储卷,网络等)的组合的抽象。

我们需要给这个部署一个名字和一个命名空间,以将它们组织起来并便于查找。我们同样会将 serviceType 设置为 NodePort 。这一步非常重要,因为在默认设置中,服务类型会被设置为 LoadBalancer。由于我们的集群现在没有负载均衡器,所以我们将无法在集群外访问我们的 WordPress 站点。

在输出数据的最后一部分,你会注意到一些关于访问你的 WordPress 站点的有用的命令。运行那些命令,你可以获取到我们的 WordPress 站点的外部 IP 地址和端口:

$ export NODE_PORT=$(kubectl get --namespace wordpress -o jsonpath="{.spec.ports[0].nodePort}" services wordpress-wordpress)
$ export NODE_IP=$(kubectl get nodes --namespace wordpress -o jsonpath="{.items[0].status.addresses[0].address}")
$ echo http://$NODE_IP:$NODE_PORT/admin

你现在访问刚刚生成的 URL(忽略 /admin 部分),就可以看到 WordPress 已经在你的 Kubernetes 集群中运行了!

扩展 WordPress

Kubernetes 等服务编排平台的一个伟大之处,在于它将应用的扩展和管理变得易如反掌。我们看一下应用的部署状态:

$ kubectl get deployments --namespace=wordpress

未分类

可以看到,我们有两个部署,一个是 Mariadb 数据库,一个是 WordPress 本身。现在,我们假设你的 WordPress 开始承载大量的流量,所以我们想将这些负载分摊在多个实例上。我们可以通过一个简单的命令来扩展 wordpress-wordpress 部署:

$ kubectl scale --replicas 2 deployments wordpress-wordpress --namespace=wordpress

再次运行 kubectl get deployments,我们现在应该会看到下面的场景:

未分类

你刚刚扩大了你的 WordPress 站点规模!超级简单,对不对?现在我们有了多个 WordPress 容器,可以在它们之中对流量进行负载均衡。想了解 Kubernetes 扩展的更多信息,参见这篇指南。

高可用

Kubernetes 等平台的的另一大特色在于,它不单单能进行方便的扩展,还可以通过自愈组件来提供高可用性。假设我们的一个 WordPress 部署因为某些原因失效了,那 Kubernetes 会立刻自动替换掉这个部署。我们可以通过删除我们 WordPress 部署的一个 pod 来模拟这个过程。

首先运行命令,获取 pod 列表:

$ kubectl get pods --namespace=wordpress

未分类

然后删除其中一个 pod:

$ kubectl delete pod wordpress-wordpress-876183909-jqc8s --namespace=wordpress

如果你再次运行 kubectl get pods 命令,应该会看到 Kubernetes 立刻换上了新的 pod (3l167)。

未分类

更进一步

我们只是简单了解了 Kubernetes 能完成工作的表面。如果你想深入研究,我建议你查看以下功能:

  • 平行扩展
  • 自愈
  • 自动更新及回滚
  • 密钥管理

你在容器平台上运行过 WordPress 吗?有没有使用过 Kubernetes(或其它容器编排平台),有没有什么好的技巧?你通常会怎么扩展你的 WordPress 站点?请在评论中告诉我们。

Kubernetes之蓝绿部署

【编者的话】毋庸置疑,Kubernetes目前已成为业内最炙手可热的容器编排框架。本文主要讲讲怎么用Kubernetes进行蓝绿部署以及如何自动化实现蓝绿部署。更多Kubernetes知识请关注DockOne其他文章。

对于那些有更多热情想投入其中的朋友,我已经在GitHub上上传了一个教程和一些示例清单。请访问https://github.com/IanLewis/ku … orial。

Kubernetes有一个非常棒的内置功能即部署(Deployments)。当您将应用程序更新到一个新版本时,部署功能能够帮您对容器进行滚动更新。滚动更新是更新应用程序的一种很好的方法,因为您的应用程序在更新期间使用的资源数量,基本和不更新时所使用的资源相同,而且滚动更新过程中对性能和可用性影响最小。

尽管如此,仍然有许多老式的应用程序在滚动更新中不能很好地运行。一些应用程序只需要部署一个新版本,并需要立即切到这个版本。因此,我们需要执行蓝/绿部署。在进行蓝/绿部署时,应用程序的一个新副本(绿)将与现有版本(蓝)一起部署。然后更新应用程序的入口/路由器以切换到新版本(绿)。然后,您需要等待旧(蓝)版本来完成所有发送给它的请求,但是大多数情况下,应用程序的流量将一次更改为新版本。

未分类

Kubernetes不支持内置的蓝/绿部署。目前最好的方式是创建新的部署,然后更新应用程序的服务以指向新的部署。接下来让我们来看看这是啥意思。

蓝部署

Kubernetes部署指定一个应用程序的一组实例。在幕后,它创建一个副本,该副本负责保持指定数量的实例运行。

未分类

我们可以通过将以下yaml保存到blue.yaml文件中来创建我们的“蓝色”部署。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-1.10
spec:
replicas: 3
template:
metadata:
  labels:
    name: nginx
    version: "1.10"
spec:
  containers: 
    - name: nginx
      image: nginx:1.10
      ports:
        - name: http
          containerPort: 80

然后可以使用kubectl命令创建部署。

$ kubectl apply -f blue.yaml

一旦我们进行了部署,我们可以通过创建一个服务来提供访问部署实例的方法。服务与部署分离,这意味着您不必在部署时明确指向服务。您需要做的是指定一个标签选择器,它的主要作用是列出构成服务的pods。当你使用部署时,通常会将其设置为与部署的pods相匹配。

在这种情况下,我们有两个标签,name=nginx和version=1.10。我们将这些设置为下面的服务的标签选择器。将下面的内容保存到service.yaml。

apiVersion: v1
kind: Service
metadata: 
name: nginx
labels: 
name: nginx
spec:
ports:
- name: http
  port: 80
  targetPort: 80
selector: 
name: nginx
version: "1.10"
type: LoadBalancer

创建服务将创建一个可在集群外访问的负载均衡器。

$ kubectl apply -f service.yaml

现在我们看看已经部署的服务,如下图。

未分类

您可以测试该服务是否可访问并获取该版本。

$ EXTERNAL_IP = $( kubectl get svc nginx -o jsonpath = “{.status.loadBalancer.ingress [*]。ip}” ) 
$ curl -s http:// $ EXTERNAL_IP / version | grep nginx 

创建绿部署

对于“绿”部署,我们将部署“蓝”部署并行的新部署。如果以下是

green.yaml……
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-1.11
spec:
replicas: 3
template:
metadata:
  labels:
    name: nginx
    version: "1.11"
spec:
  containers: 
    - name: nginx
      image: nginx:1.11
      ports:
        - name: http
          containerPort: 80

……我可以像这样创建新的部署。

$ kubectl apply -f green.yaml

现在我有两个部署,但服务仍然指向“蓝”部署。接下来我们要怎么做呢?

未分类

更新应用程序

要切换到“绿”部署,我们将更新服务的选择器。编辑service.yaml并将选择器版本更改为“1.11”。这将使它与“绿”部署中的pods匹配。

apiVersion: v1
kind: Service
metadata: 
name: nginx
labels: 
name: nginx
spec:
ports:
- name: http
  port: 80
  targetPort: 80
selector: 
name: nginx
version: "1.11"
type: LoadBalancer

执行下面的命令将更新现有的nginx服务。

$ kubectl apply -f service.yaml

现在我们再看看已经部署的,如下图。

未分类

更新服务的选择器将立即被应用,因此您应该看到新版本的nginx正在提供服务。

$ EXTERNAL_IP = $( kubectl get svc nginx -o jsonpath = “{.status.loadBalancer.ingress [*]。ip}” ) 
$ curl -s http:// $ EXTERNAL_IP / version | grep nginx

自动化

您可以通过一些脚本来自动化跑您的蓝/绿部署。以下脚本将使用服务的名称,要部署的版本以及绿色部署的yaml文件的路径,并使用kubectl运行完整的蓝/绿部署,以从API中输出原始JSON,并使用jq进行解析。通过status.conditions在更新服务定义之前检查部署对象,等待绿部署准备就绪。

脚本为简单起见做出了一些假设,例如期望部署的名称为形式 – 并且存在用于选择器的name和version标签。kubectl是超级灵活的,你可以用它写你自己需要的任何东东。

#!/bin/bash
bg-deploy.sh <servicename> <version> <green-deployment.yaml>

Deployment name should be <service>-<version>

DEPLOYMENTNAME=$1-$2
SERVICE=$1
VERSION=$2
DEPLOYMENTFILE=$3

kubectl apply -f $DEPLOYMENTFILE
Wait until the Deployment is ready by checking the MinimumReplicasAvailable condition.

READY=$(kubectl get deploy $DEPLOYMENTNAME -o json | jq '.status.conditions[] | select(.reason == "MinimumReplicasAvailable") | .status' | tr -d '"')
while [[ "$READY" != "True" ]]; do
READY=$(kubectl get deploy $DEPLOYMENTNAME -o json | jq '.status.conditions[] | select(.reason == "MinimumReplicasAvailable") | .status' | tr -d '"')
sleep 5
done
Update the service selector with the new version

kubectl patch svc $SERVICE -p "{"spec":{"selector": {"name": "${SERVICE}", "version":"${VERSION}"}}}"
echo "Done."

最后,真心希望Kubernetes可以原生支持蓝/绿部署,但这美好时刻来临之前,您可以通过上面的方式来实现自动化。如需要联系那些关心Kubernetes应用程序部署的童鞋,请查看Kubernetes Slack中的#sig-apps频道 。

Kubernetes DaemonSet的滚动升级

DaemonSet好比Kubernetes集群Node的守护进程,可以保证在每个Node上(或者一部分Node上)都运行同一个Pod。 目前我们的线上环境主要用到以下两个DaemonSet:

  • kube-flannel-ds 这个是部署Kubernetes集群时选用的是flannel network add-on
  • fluent-bit 这个是用来在部署在各个Node上,收集各个Node上容器的日志。我们选用的日志收集方案是EFK(Elasticsearch+Fluent-bit+Kibana),后边有时间再写点fluent-bit的内容

我们目前线上Kubernetes的版本总是落后最新的release版本,例如现在Kubernetes最新是1.7,我们使用1.6.x。但是我们注意到Kubernetes 1.7中很多外部组件、Addon都做了更新。我们在使用1.6.x的过程中会考虑提前升级这些组件,以便于后续顺利将Kubernetes升级到1.7。DaemonSet的升级就是需要考虑的。

滚动升级特性是Kubernetes服务发布的一个很有用的特性,而Kubernetes 1.6+支持DaemonSet的滚动升级,1.7开始支持DaemonSet滚动升级的回滚。下面一起来学习一个DaemonSet的滚动升级。

DaemonSet的升级策略

DaemonSet目前有两种升级策略,可以通过.spec.updateStrategy.type指定:

  • OnDelete: 该策略表示当更新了DaemonSet的模板后,只有手动删除旧的DaemonSet Pod才会创建新的DaemonSet Pod
  • RollingUpdate: 该策略表示当更新DaemonSet模板后会自动删除旧的DaemonSet Pod并创建新的DaemonSetPod

体验DaemonSet的滚动升级

要使用DaemonSet的滚动升级,需要 .spec.updateStrategy.type设置为RollingUpdate。 也可以进一步设置.spec.updateStrategy.rollingUpdate.maxUnavailable,默认值为1; 设置.spec.minReadySeconds默认为0,用于指定认为DaemoSet Pod启动可用所需的最小的秒数。

下面以我们测试环境中的flannel DaemonSet为例,体验从0.7.1升级到0.8.0的过程。

之前0.7.1的manifest文件没有设置更新策略为滚动升级,首先设置一下,同时注意修改flannel镜像版本为0.8.0:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: kube-flannel-ds
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  updateStrategy:
        type: RollingUpdate
  template:
  ......

然后执行:

kubectl apply -f kube-flannel.yml

可以使用下面的命令查看滚动升级状态:

kubectl rollout status ds/kube-flannel-ds -n kube-system
Waiting for rollout to finish: 1 out of 4 new pods have been updated...
Waiting for rollout to finish: 2 out of 4 new pods have been updated...
Waiting for rollout to finish: 3 out of 4 new pods have been updated...
daemonset "kube-flannel-ds" successfully rolled out

Kubernetes的Cron Job定时任务小试

Kubernetes集群使用Cron Job管理基于时间的作业,可以在指定的时间点执行一次或在指定时间点执行多次任务。 一个Cron Job就好像Linux crontab中的一行,可以按照Cron定时运行任务。

定时任务对我们并不陌生,例如Linux的crontab,各种编程语言都内置了定时任务支持,这在我们应用开发中比较常见,但这种定时任务在分布式系统中使用会有限制,因此需要分布式计划任务。 Kubernetes的CronJob可以理解为Kubernetes对分布式计划任务的支持。

在使用Cron Job之前需要确认Kubernetes集群的版本>=1.5,因为它还处于alpha,所以还需要对kube-apiserver加入启动参数–runtime-config=batch/v2alpha1=true,开启batch/v2alpha1。 下面我们来试验一下,试验的Kubernetes集群的版本为1.6.8。

在加入启动参数–runtime-config=batch/v2alpha1=true后,要重启kube-apiserver, kube-controller-manager, kube-scheduler,创建crontab才会被调度

创建Cron Job

创建一个简单的CronJob,每隔1分钟打印当前的时间并”say Hello”,cronjob.yaml:

apiVersion: batch/v2alpha1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: alpine
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

下面创建这个CronJob:

kubectl create -f cronjob.yaml
cronjob "hello" created

查看这个CronJob的状态:

kubectl get cronjob hello
NAME      SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE
hello     */1 * * * *   False     0         <none>

从上面的输出看这个cronjob还没有被调度,等大约1分钟再次查看:

kubectl get jobs --watch
NAME               DESIRED   SUCCESSFUL   AGE
hello-1503321060   1         1            2m
hello-1503321120   1         1         1m
hello-1503321180   1         1         36s


kubectl get cronjob
NAME      SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE
hello     */1 * * * *   False     0         Mon, 21 Aug 2017 21:14:00 +0800

删除Cron Job

 kubectl delete cronjob hello
cronjob "hello" deleted

kubectl delete -f cronjob.yaml

删除命令会停止已经创建出来的作业,当时正在运行的作业不会被被停止,同时Job和Pod不会被删除:

kubectl get jobs
NAME               DESIRED   SUCCESSFUL   AGE
hello-1503321060   1         1            7m
hello-1503321120   1         1            6m
hello-1503321180   1         1            5m
hello-1503321240   1         1            4m
hello-1503321300   1         1            3m
hello-1503321360   1         1            2m
hello-1503321420   1         1            1m

需要手动删除上面的job,job被删除,它创建出来的Pod也会被删除掉。

使用kubectl delete jobs –all可以删除当前namespaces下所有的job

当前Cron Job的限制

当前一个CronJob在执行期间“大约”创建一个Job,之所以说“大约”是因为在特殊的情况下可能会创建两个或没有Job被创建。Kubernetes官方正在试图使这种情况尽量少发生,但目前还不能保证完全杜绝。 因此如果我们现在使用它,那么Job应该被我们设计成幂等的。

GitHub是如何无缝迁移到Kubernetes的?

【编者的话】全球最大的代码托管和编程社交网络GitHub,近期已经在开发、SRE等团队的配合下将服务切换到Kubernetes,因为其坐拥千万用户和亿级代码仓库,这可不是一个小工程,文章介绍了GitHub迁移到Kubernetes的整个过程。

在过去的一年中,GitHub逐步发展了运行Ruby On Rails应用的基础设施,该应用负责github.com和api.github.com。目前所有的WEB和API请求都由运行在GitHub Metal Cloud上的Kubernetes容器集群中。

将关键应用迁移到Kubernetes是一个非常有意思的挑战,今天就给大家分享下:

为什么尝试改变——释放SRE工程师

在此之前,主要的Ruby On Rails应用(github/github)还类似于8年前:“Unicorn processes”由Ruby进程管理器调用运行在Puppet-managed的“God”。同样的,部署ChatOps如同第一次引入时所作:Capistrano在每个前端服务器上建立SSH连接,然后更新代码,重新启动应用,当峰值请求负载超过可用的前端CPU容量时,GitHub站点的SRE会释放额外的容量,并将其添加到活动前端服务器池当中。

未分类

在近几年中,虽然基本生产方法没有太大的变化,不过GitHub本身有了很多改变如:新的功能、更大的社区、更多的员工、以及更多的需求。所以这也就产生了很多新的问题,许多团队想将其负责的功能从某个大的应用中提取出来,形成一个可以独立运行和部署的小型服务。伴随着运行服务数量的增加,SRE团队开始为数十个其他应用提供类似的配置,从而增加了在服务器维护、配置和其他工作上花费的时间,但这些工作又和改进整个GitHub的工作流程没有直接关联。

新服务因为它们的复杂性和SRE团队的时间问题,需要几天、几周或者几个月的时间来进行部署,随着时间的推移,一些问题逐渐显现:这种方法并没有为工程师提供他们需要的灵活性,以继续构建世界级的服务。

工程师们需要一个自助平台,可以在上面试验、部署和扩展新的服务,还需要同样的平台来满足Ruby On Rails应用的需求,这样工程师或机器人就能以秒、天或更长的时间来分配额外的计算机资源去响应需求的变化。

为了满足这些需求,SRE、平台和开发者体验团队开始了一个联合项目:每天数十次地将github.com和api.github.com的代码部署到Kubernetes集群。

为什么是Kubernetes?

为了评估“平台即服务”的工具,GitHub仔细研究了Kubernetes,它是Google的一个项目,用于自动化部署、扩展和管理容器化应用的开源系统,通过以下几点为Kubernetes特性做了评估:该项目获得了火热的开源社区支持,首次运行实践(允许部署小型集群和应用在最初的几个小时),大量关于设计的经验。

因此迅速地扩大了实验力度和范围:立了一个小的项目去构建Kubernetes集群和部署工具,用来支持即将到来的Hack Week从而获得一些实际场景中的经验,GitHub内部对这个项目反映非常积极。

为什么从github/github开始?

在项目最初阶段,GitHub做了一个深思熟虑的决定,关键性工作负载的:github/github迁移,许多因素促成了这一决定,比较重要的几点是:

  • 深入了解GitHub中运行的这个应用软件在迁移过程中很有用
  • 需要自助扩展工具来处理持续的增长
  • 希望确保开发习惯和模式适用于大型应用和较小的服务
  • 可以更好地将应用与开发、Staging、生产、Enterprise、和其他环境隔离
  • 迁移一个关键的、高知名度的工作负载可以激发信心,鼓励GitHub进一步采用Kubernetes

考虑到我们计划迁移的工作负载极为关键,我们需要在利用其交付实际生产流量之前建立起充分的运营信心。

通过引入审查实验室以实现快速迭代并建立信心

作为迁移的一部分,进行了一些设计以及原型,并验证了前端服务器使用Kubernetes的基础服务如:Pod、部署和服务。通过在容器中运行gitub/github现有的测试套件,可以对这种新设计进行一些验证,但仍需观察这个容器是如何成为更大的Kubernetes资源一部分的,很快就清楚地认识到,在验证阶段,对Kubernetes和打算运行的服务进行探索性测试环境是必备的。

与此同时,项目成员观察到,现有的github/github抓取请求的模式已经开始显示出增长十分困难的迹象,部署速度和工程师的数量成正比,使用几个额外的部署环境作为验证拉取请求到github/github的部分过程也是如此,在工作高峰时,功能齐全的部署环境数量往往是固定的,这就降低了部署拉取请求的过程,工程师门经常要求在“Branch Lab”中测试更多的子系统,同时允许多名工程师同时进行部署,但每个工程师只能启动一个“Unicorn Process”,所以只在测试API和UI变更时有用,因为这些需求重叠很多,所以可以将这些项目结合起来,并开始在github/github上开发一个新的基于Kubernetes/github的部署环境,被称之为:Review Lab。

在构建Review Lab的过程中,还发布了几个子项目:

  • 在AWS VPC上运行的Kubernetes集群管理使用了Terraform & Kops
  • 一组Bash集成测试使用短暂的Kubernetes集群,后来在项目开始时大量使用,增强对Kuberbetes的信心。
  • 一个github Dockerfile/github
  • 对内部CI平台的增强,用来支持构建和将容器发布到容器注册中心
  • YAML表示50+Kubernetes资源,签入github/github
  • 对内部部署应用的增强,支持将Kubernetes的资源从一个存储库部署到Kubernetes的命名空间,以及从内部存储库中创建Kubernetes
  • 该服务结合了Haproxy和Consul-Template,将Unicorn Pods路由到现有的服务,发布服务信息。
  • 一种读取Kubernetes事件的服务,并将异常事件发送给内部服务跟踪系统
  • 一种名为kube-me且与Rpc兼容的服务,通过聊天向用户公开一组有限的kubectl命令。

最终的结果是一个基于聊天的界面,用于为任何拉取请求创建GitHub的独立部署,一旦请求通过了所有需要的CI任务,用户即可部署他们的请求:

未分类

如同之前的“Branch Lab”一样,实验室在最后一次部署后就被清理掉,由于每个实验室都是在其Kubernetes名称空间中创建的,清理工作就像删除名称空间一样简单,部署系统会在需要时自动执行。

Review Lab是一个成功的项目,积累了许多经验和成果,在为工程师提供这种环境之前,还为Kubernetes集群设计提供了必要的验证基础和原型环境,以及Kubernetes资源的设计和配置,这些资源现在用以描述github/github的工作负载,在发布后,它帮助工程师建立了信心,GitHub非常满意这个环境赋予工程师实验和通过自助的方式去解决问题。

未分类

Metal Cloud上的Kubernetes

随着Review Lab的发布后,注意力就转移到了github.com上,为了满足关键服务的性能的可靠性需求(依赖于低延迟访问其他数据服务),需要构建Kubernetes的基础设施,去支持在物理数据中心和POP中运行的云计算,同样,有十几个子项目:

  • 关于容器网络,因为一个及时且详尽的帖子,GitHub选择了Calico,其提供了需要在IPIP模式下快速发送一个集群的功能,与此同时也提供了可以在以后的网络基础设施中进行探索的灵活性。
  • 通过多次阅读Kelesyhightower写的《Kubernetes the hard way》,GitHub将一些手动操作的服务器组装到了一个临时的Kubernetes集群中,此集群通过了相关测试。
  • 还构建了一些小工具,为每个集群生成所需的CA和配置,格式可以由Puppet和Secret Systems 使用。
  • 对两个实例配置进行了处理:Kubernetes节点和Kubernetes Apiservers,这种方式允许用户提供已经配置的集群名称,以便在规定的时间内引入。
  • 构建了一个小型的Go服务,用于消耗容器日志,将Key/Value格式的元数据附加到每一行,并将它们发送到主机的本地Syslog端点。
  • 加强内部负载均衡服务,用来支持Kubernetes Node Port。

这些工作并没有白费,都通过了内部验收测试的集群,因此,GitHub的信心十足,同样的一组Inputs(由Review Lab使用的Kubernetes资源),相同的数据集(网络服务Review Lab连接到VPN上),同样的工具都会产生类似的结果,不到一周,虽然大部分的时间都花费在了内部通信和排序上,但对迁移产生了非常重大的影响:可以将整个工作负载从运行在AWS上的Kubernetes集群迁移到一个运行在GitHub数据中的集群。

提升信心

Kubernetes集群在Github Metal Cloud上的成功和可复制性,所以是时候对“Unicorn”进行部署来替代当前前端服务器池了,在GitHub,工程师及其团队通过创建一个Flipper特性去验证新功能是很常见的做法,若可行即选择它,然后加强部署系统再去部署一套新的Kubernetes资源,github-produciton名称空间和现有的生产服务器,提高GLB支持员工请求路由到不同的后端:基于Flipper-infuenced的cookie,允许员工在任务控制栏的一个按钮上选择实验Kubernetes后端。

未分类

来自内部用户的负载可以帮助发现问题、修复BUG,并开始适应Kubernetes的生产,在此期间,通过模拟未来想要执行的应用、编写运行手册和执行Failure Tests来增强信心,还将少量的生产流量路由到这个集群,去确认对于负载下性能和可靠性的设定,从每秒100个请求开始,然后扩展到github.com和api.github.com请求的10%,在几个模拟试验中曾短暂地停止用来重新评估完全迁移的风险。

未分类

Cluster Groups

因为一些Failure Tests导致了意料之外的结果,特别是模拟单个Apiserver节点故障的测试破坏了集群,这种方式对运行工作负载的可用性造成了负面影响,根据调查显示,这些测试没有取得决定性的效果,但帮助识别出相关的破坏可能是一个相互作用的各种客户连接到Kubernetes Apiserver(像Calico-Agent Kubelet Kube-Proxy,Kube-Controller-Manager)和内部负载均衡器的行为在一个Apiserver节点中的失败,因为检测到Kuberntes集群降级可能会破坏服务,所以开始关注了每个站点上运行的关键应用,并自动化地将请求从一个不健康的集群迁移到其他健康的集群。

类似的工作已经放在GitHub的流程图中,支持此应用部署到多个独立运营的网站,和其他积极的方式进行取舍。最终选定的设计是:使用部署系统支持多个“分区”增强它通过一个定制的支持提供集群乏味内配置Kubernetes资源注释,放弃现有的联合解决方案,允许使用业务逻辑已经出现在GitHub的部署系统。

从10%到100%

有了集群组,GitHub逐渐将前端服务器转换为Kubernetes节点,并增加了路由到Kubernetes的流量,与其他一些工程师团队一起,在一个多月的时间内完成了前端的转换,同时,在这此期间保持了预计的性能和可接受的错误率。

未分类

在迁移过程中,遇到了一直持续至今的问题:在高负载/或高利用率的容器中,一些Kubernetes节点会出现内核错误并会重启,虽然GitHub对这种情况不是很满意,而且进行了最高优先级的排查,但还是很高兴地看到Kubernetes能够自动地绕过这些故障,并继续在错误范围内服务流量。

GitHub进行了一些Failure Tests,模拟了与echo c/proc/sysrq触发相似的内核错误,这是一个很有用的补充。

未来

本文的灵感来自于将此应用迁移到Kubernetes的经验,并且期待着更多的迁移,虽然在第一次迁移的范围被有意限制在无状态的工作负载中,但对在Kubernetes上尝试运行有状态的服务模式抱有很大兴趣。

在项目的最后阶段,GitHub还发布了一个工作流,用于将新的应用和服务部署到类似的Kubernetes集群中。工程师们几个月以来已经在此集群中部署了数十个应用,每一个都需要SRE的配置管理和支持,有了自助式的应用供应工作流,SRE可以将更多的时间花在向工程团队的其他成员交付基础设施产品上去支持最佳实践,为每个人构建更快,更有弹性的GitHub体验。

使用open-falcon cAdvisor实现对k8s(kubernetes)集群的监控

1. 前言

当我们的k8s要面临落地时,监控和日志肯定时不可缺少的。它主要为了帮助系统运维人员事前及时预警发现故障,事后通过翔实的数据追查定位问题。

2. 可选方案

  • Heapster(数据采集自cAdvisor)+Influxdb(存储)+Grafana(展示)
    这套方案缺点是没有报警功能

  • Prometheus+Grafana
    参考:http://blog.csdn.net/zqg5258423/article/details/53119009

  • open-falcon+cAdvisor
    本文主要介绍这种方式,官方文档:https://book.open-falcon.org/zh/intro/index.html
    实际案例:https://zhuanlan.zhihu.com/p/27697789

3. openfalcon架构图

未分类

可以看到,它的核心组件是agent,它需要运行在每台node上获取数据,所以对于k8s集群的监控,我们会主要对agent做二次开发,嵌入cAdvisor代码,实现对容器的监控。

4. 涉及的组件及简要说明

  • agent 监控信息上报 需要二次开发
  • transfer 监控信息转发
  • graph 监控信息存储
  • query 监控信息查询
  • hbs 心跳服务器
  • judge 报警事件判断
  • alarm 报警事件处理 需要二次开发
  • sender 报警事件发送 需要二次开发
  • nodata 假数据填充
  • Redis 报警事件队列

5. 监控流程

Agent监控各个主机和容器的信息(嵌入了cadvisor代码),并将数据通过transfer转发存入graph,控制台通过http方式访问query api查询graph获取数据。

6. 报警流程

不再单独部署portal模块,报警策略信息由控制台报警模块提供并写入MySQL的portal数据库,供hbs读取,hbs加载mysql portal库策略数据并关联到endpoint后提供给Judge组件使用,transfer转发到judge的每一条数据会触发相关策略进行判断将结果放入redis,alarm从redis中获取报警event后保存至控制台数据库,以提供控制台
前端展示报警信息

7. 监控系统本身的健康检查

对于k8s集群主机的基础指标监控,容器的基本指标,我们用falcon来监控, 而k8s核心组件和falcon核心组件的可用性监控,用一个独立的工具scripts来监控,它会部署在多个数据中心的多台主机上进行探测。

对监控系统的监控不能再依赖于监控系统, 使用独立工具的好处在于没有依赖,简单稳定,可用性高。而多中心多主机的部署也是为了保证工具的可用性。

解决Kubernetes(k8s) 1.7.3 kube-apiserver频繁异常重启的问题

近期将之前的一个用kube-up.sh安装的Kubernetes 1.3.7的环境更换为最新发布的用kubeadm安装的Kubernetes 1.7.3版本。新版本的安装过程和之前的采用kubeadm安装的k8s 1.5.x、1.6.x版本类似,这里不赘述了。但在安装Dashboard后,发现了一些问题,这里记录一下解决的过程。

一、第一个问题

我们先来做一下回顾。在《解决Kubernetes 1.6.4 Dashboard无法访问的问题》一文中,我们通过把用户admin bind到cluster-admin这个clusterrole角色上使得dashboard得以正常访问。但访问几次后,我发现了一个问题:那就是用safari访问dashboard时,浏览器可以正常弹出鉴权对话框,让我输入用户名和密码;但用chrome访问时,总是无法弹出鉴权对话框,而直接显示如下错误:

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

kube-apiserver身份验证文档中对anonymous requests做了说明:对于没有被其他身份验证方法拒绝的requests,kube-apiserver会为这样的request赋予用户名: system:anonymous和用户group: system:unauthenticated,这个request将继续流向后面的环节:authorization和admission-control,直到被后面的环节拒绝,返回失败应答。这一些都源于k8s 1.6以后的版本中,kube-apiserver的命令行选项:–anonymous-auth的默认值改为了true,即允许anonymous request的存在,因此上面chrome在访问kube-apiserver时,不输入user、password也能继续下面的环节,这就是第一个问题及其原因。

二、关闭匿名请求的身份验证权

解决上面这个问题,最直接的方法就是关闭匿名请求的身份验证权,即不接受匿名请求。我们通过在/etc/kubernetes/manifests/kube-apiserver.yaml中添加下面一行来实现:

spec:
  containers:
  - command:
    - kube-apiserver
    - --anonymous-auth=false

/etc/kubernetes/manifests/kube-apiserver.yaml被修改后,kubelet会重启kube-apiserver。重启后,我再用chrome访问dashboard,身份验证对话框就出现在眼前了。

三、kube-apiserver周期性异常重启

一直以为问题到这里就解决了。但随后又发生了一个更为严重的问题,那就是:kube-apiserver定期重启,并牵连kube-controller-manager和kube-scheduler的status也不正常了。

通过kubectl describe查看状态异常的kube-apiserver pod,发现如下输出:

root@yypdcom2:# kubectl describe pods/kube-apiserver-yypdcom2 -n kube-system|grep health
    Liveness:        http-get https://127.0.0.1:6443/healthz delay=15s timeout=15s period=10s #success=1 #failure=8

可以看到liveness check有8次failure!8次是kube-apiserver的failure门槛值,这个值在/etc/kubernetes/manifests/kube-apiserver.yaml中我们可以看到:

livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 15
      timeoutSeconds: 15

这样,一旦failure次数超限,kubelet会尝试Restart kube-apiserver,这就是问题的原因。那么为什么kube-apiserver的liveness check会fail呢?这缘于我们关闭了匿名请求的身份验证权。还是来看/etc/kubernetes/manifests/kube-apiserver.yaml中的livenessProbe段,对于kube-apiserver来说,kubelet会通过访问: https://127.0.0.1:6443/healthz的方式去check是否ok?并且kubelet使用的是anonymous requests。由于上面我们已经关闭了对anonymous-requests的身份验证权,kubelet就会一直无法访问kube-apiserver的/healthz端点,导致kubelet认为kube-apiserver已经死亡,并尝试重启它。

四、调整/healthz检测的端点

我们既要保留 –anonymous-auth=false,还要保证kube-apiserver稳定运行不重启,我们就需要调整kube-apiserver的livenessProbe配置,将liveness probe的endpoint从

https://127.0.0.1:6443/healthz

改为:

http://127.0.0.1:8080/healthz

具体对/etc/kubernetes/manifests/kube-apiserver.yaml的修改是:

spec:
  containers:
  - command:
    - kube-apiserver
    - --anonymous-auth=false
    ... ...
    - --insecure-bind-address=127.0.0.1
    - --insecure-port=8080

   livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 8080
        scheme: HTTP
      initialDelaySeconds: 15
      timeoutSeconds: 15
... ...

我们不再用anonymous-requests,但我们可以利用–insecure-bind-address和–insecure-port。让kubelet的请求到insecure port,而不是secure port。由于insecure port的流量不会受到身份验证、授权等功能的限制,因此可以成功probe到kube-apiserver的liveness,kubelet不会再重启kube-apiserver了。

Kubernetes(k8s)总架构图及各组件介绍

一、Kubernetes的总架构图

未分类

二、Kubernetes各个组件介绍

(一)kube-master[控制节点]

master的工作流程图

未分类

  • Kubecfg将特定的请求,比如创建Pod,发送给Kubernetes Client。

  • Kubernetes Client将请求发送给API server。

  • API Server根据请求的类型,比如创建Pod时storage类型是pods,然后依此选择何种REST Storage API对请求作出处理。

  • REST Storage API对的请求作相应的处理。

  • 将处理的结果存入高可用键值存储系统Etcd中。

  • 在API Server响应Kubecfg的请求后,Scheduler会根据Kubernetes Client获取集群中运行Pod及Minion/Node信息。

  • 依据从Kubernetes Client获取的信息,Scheduler将未分发的Pod分发到可用的Minion/Node节点上。

1、API Server[资源操作入口]

  • 提供了资源对象的唯一操作入口,其他所有组件都必须通过它提供的API来操作资源数据,只有API Server与存储通信,其他模块通过API Server访问集群状态。

第一,是为了保证集群状态访问的安全。

第二,是为了隔离集群状态访问的方式和后端存储实现的方式:API Server是状态访问的方式,不会因为后端存储技术etcd的改变而改变。

  • 作为kubernetes系统的入口,封装了核心对象的增删改查操作,以RESTFul接口方式提供给外部客户和内部组件调用。对相关的资源数据“全量查询”+“变化监听”,实时完成相关的业务功能。

更多API Server信息请参考:Kubernetes核心原理(一)之API Server

2、Controller Manager[内部管理控制中心]

  • 实现集群故障检测和恢复的自动化工作,负责执行各种控制器,主要有:

endpoint-controller:定期关联service和pod(关联信息由endpoint对象维护),保证service到pod的映射总是最新的。

replication-controller:定期关联replicationController和pod,保证replicationController定义的复制数量与实际运行pod的数量总是一致的。

更多Controller Manager信息请参考:Kubernetes核心原理(二)之Controller Manager

3、Scheduler[集群分发调度器]

  • Scheduler收集和分析当前Kubernetes集群中所有Minion节点的资源(内存、CPU)负载情况,然后依此分发新建的Pod到Kubernetes集群中可用的节点。

  • 实时监测Kubernetes集群中未分发和已分发的所有运行的Pod。

  • Scheduler也监测Minion节点信息,由于会频繁查找Minion节点,Scheduler会缓存一份最新的信息在本地。

  • 最后,Scheduler在分发Pod到指定的Minion节点后,会把Pod相关的信息Binding写回API Server。

更多Scheduler信息请参考:Kubernetes核心原理(三)之Scheduler

(二)kube-node[服务节点]

kubelet结构图

未分类

1、Kubelet[节点上的Pod管家]

  • 负责Node节点上pod的创建、修改、监控、删除等全生命周期的管理

  • 定时上报本Node的状态信息给API Server。

  • kubelet是Master API Server和Minion之间的桥梁,接收Master API Server分配给它的commands和work,与持久性键值存储etcd、file、server和http进行交互,读取配置信息。

  • 具体的工作如下:

设置容器的环境变量、给容器绑定Volume、给容器绑定Port、根据指定的Pod运行一个单一容器、给指定的Pod创建network 容器。

同步Pod的状态、同步Pod的状态、从cAdvisor获取Container info、 pod info、 root info、 machine info。

在容器中运行命令、杀死容器、删除Pod的所有容器。

2、Proxy[负载均衡、路由转发]

  • Proxy是为了解决外部网络能够访问跨机器集群中容器提供的应用服务而设计的,运行在每个Node上。Proxy提供TCP/UDP sockets的proxy,每创建一种Service,Proxy主要从etcd获取Services和Endpoints的配置信息(也可以从file获取),然后根据配置信息在Minion上启动一个Proxy的进程并监听相应的服务端口,当外部请求发生时,Proxy会根据Load Balancer将请求分发到后端正确的容器处理。

  • Proxy不但解决了同一主宿机相同服务端口冲突的问题,还提供了Service转发服务端口对外提供服务的能力,Proxy后端使用了随机、轮循负载均衡算法。

3、kubectl(kubelet client)[集群管理命令行工具集]

通过客户端的kubectl命令集操作,API Server响应对应的命令结果,从而达到对kubernetes集群的管理。

kubernetes(k8s) 1.7.3 calico网络和Master ha安装说明

kubernetes 1.7.3

基于 二进制 文件部署 本地化 kube-apiserver, kube-controller-manager , kube-scheduler 我这边配置 既是 master 也是 nodes

环境说明

k8s-master-1: 10.6.0.140
k8s-master-2: 10.6.0.187
k8s-master-3: 10.6.0.188

初始化环境

hostnamectl --static set-hostname hostname

10.6.0.140 - k8s-master-1
10.6.0.187 - k8s-master-2
10.6.0.188 - k8s-master-3
#编辑 /etc/hosts 文件,配置hostname 通信

vi /etc/hosts

10.6.0.140 k8s-master-1
10.6.0.187 k8s-master-2
10.6.0.188 k8s-master-3

创建 验证

这里使用 CloudFlare 的 PKI 工具集 cfssl 来生成 Certificate Authority (CA) 证书和秘钥文件。

安装 cfssl

mkdir -p /opt/local/cfssl

cd /opt/local/cfssl

wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
mv cfssl_linux-amd64 cfssl

wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
mv cfssljson_linux-amd64 cfssljson

wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
mv cfssl-certinfo_linux-amd64 cfssl-certinfo

chmod +x *

创建 CA 证书配置

mkdir /opt/ssl

cd /opt/ssl

/opt/local/cfssl/cfssl print-defaults config > config.json

/opt/local/cfssl/cfssl print-defaults csr > csr.json
# config.json 文件

{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
        "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ],
        "expiry": "87600h"
      }
    }
  }
}
# csr.json 文件

{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "ShenZhen",
      "L": "ShenZhen",
      "O": "k8s",
      "OU": "System"
    }
  ]
}

生成 CA 证书和私钥

cd /opt/ssl/

/opt/local/cfssl/cfssl gencert -initca csr.json | /opt/local/cfssl/cfssljson -bare ca


[root@k8s-master-1 ssl]# ls -lt
总用量 20
-rw-r--r-- 1 root root 1005 7月   3 17:26 ca.csr
-rw------- 1 root root 1675 7月   3 17:26 ca-key.pem
-rw-r--r-- 1 root root 1363 7月   3 17:26 ca.pem
-rw-r--r-- 1 root root  210 7月   3 17:24 csr.json
-rw-r--r-- 1 root root  292 7月   3 17:23 config.json

分发证书

# 创建证书目录
mkdir -p /etc/kubernetes/ssl

# 拷贝所有文件到目录下
cp * /etc/kubernetes/ssl

# 这里要将文件拷贝到所有的k8s 机器上

scp * 10.6.0.187:/etc/kubernetes/ssl/

scp * 10.6.0.188:/etc/kubernetes/ssl/

etcd 集群

etcd 是k8s集群的基础组件

安装 etcd

yum -y install etcd

创建 etcd 证书

cd /opt/ssl/

vi etcd-csr.json

{
  "CN": "etcd",
  "hosts": [
    "127.0.0.1",
    "10.6.0.140",
    "10.6.0.187",
    "10.6.0.188"
  ],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "ShenZhen",
      "L": "ShenZhen",
      "O": "k8s",
      "OU": "System"
    }
  ]
}
# 生成 etcd   密钥

/opt/local/cfssl/cfssl gencert -ca=/opt/ssl/ca.pem 
  -ca-key=/opt/ssl/ca-key.pem 
  -config=/opt/ssl/config.json 
  -profile=kubernetes etcd-csr.json | /opt/local/cfssl/cfssljson -bare etcd
# 查看生成

[root@k8s-master-1 ssl]# ls etcd*
etcd.csr  etcd-csr.json  etcd-key.pem  etcd.pem
# 拷贝到etcd服务器

# etcd-1 
cp etcd*.pem /etc/kubernetes/ssl/

# etcd-2
scp etcd* 10.6.0.187:/etc/kubernetes/ssl/

# etcd-3
scp etcd* 10.6.0.188:/etc/kubernetes/ssl/



# 如果 etcd 非 root 用户,读取证书会提示没权限

chmod 644 /etc/kubernetes/ssl/etcd-key.pem

修改 etcd 配置

修改 etcd 启动文件 /usr/lib/systemd/system/etcd.service

# etcd-1


vi /usr/lib/systemd/system/etcd.service


[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
WorkingDirectory=/var/lib/etcd/
User=etcd
# set GOMAXPROCS to number of processors
ExecStart=/usr/bin/etcd 
  --name=etcd1 
  --cert-file=/etc/kubernetes/ssl/etcd.pem 
  --key-file=/etc/kubernetes/ssl/etcd-key.pem 
  --peer-cert-file=/etc/kubernetes/ssl/etcd.pem 
  --peer-key-file=/etc/kubernetes/ssl/etcd-key.pem 
  --trusted-ca-file=/etc/kubernetes/ssl/ca.pem 
  --peer-trusted-ca-file=/etc/kubernetes/ssl/ca.pem 
  --initial-advertise-peer-urls=https://10.6.0.140:2380 
  --listen-peer-urls=https://10.6.0.140:2380 
  --listen-client-urls=https://10.6.0.140:2379,http://127.0.0.1:2379 
  --advertise-client-urls=https://10.6.0.140:2379 
  --initial-cluster-token=k8s-etcd-cluster 
  --initial-cluster=etcd1=https://10.6.0.140:2380,etcd2=https://10.6.0.187:2380,etcd3=https://10.6.0.188:2380 
  --initial-cluster-state=new 
  --data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
# etcd-2


vi /usr/lib/systemd/system/etcd.service


[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
WorkingDirectory=/var/lib/etcd/
User=etcd
# set GOMAXPROCS to number of processors
ExecStart=/usr/bin/etcd 
  --name=etcd2 
  --cert-file=/etc/kubernetes/ssl/etcd.pem 
  --key-file=/etc/kubernetes/ssl/etcd-key.pem 
  --peer-cert-file=/etc/kubernetes/ssl/etcd.pem 
  --peer-key-file=/etc/kubernetes/ssl/etcd-key.pem 
  --trusted-ca-file=/etc/kubernetes/ssl/ca.pem 
  --peer-trusted-ca-file=/etc/kubernetes/ssl/ca.pem 
  --initial-advertise-peer-urls=https://10.6.0.187:2380 
  --listen-peer-urls=https://10.6.0.187:2380 
  --listen-client-urls=https://10.6.0.187:2379,http://127.0.0.1:2379 
  --advertise-client-urls=https://10.6.0.187:2379 
  --initial-cluster-token=k8s-etcd-cluster 
  --initial-cluster=etcd1=https://10.6.0.140:2380,etcd2=https://10.6.0.187:2380,etcd3=https://10.6.0.188:2380 
  --initial-cluster-state=new 
  --data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
# etcd-3


vi /usr/lib/systemd/system/etcd.service


[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
WorkingDirectory=/var/lib/etcd/
User=etcd
# set GOMAXPROCS to number of processors
ExecStart=/usr/bin/etcd 
  --name=etcd3 
  --cert-file=/etc/kubernetes/ssl/etcd.pem 
  --key-file=/etc/kubernetes/ssl/etcd-key.pem 
  --peer-cert-file=/etc/kubernetes/ssl/etcd.pem 
  --peer-key-file=/etc/kubernetes/ssl/etcd-key.pem 
  --trusted-ca-file=/etc/kubernetes/ssl/ca.pem 
  --peer-trusted-ca-file=/etc/kubernetes/ssl/ca.pem 
  --initial-advertise-peer-urls=https://10.6.0.188:2380 
  --listen-peer-urls=https://10.6.0.188:2380 
  --listen-client-urls=https://10.6.0.188:2379,http://127.0.0.1:2379 
  --advertise-client-urls=https://10.6.0.188:2379 
  --initial-cluster-token=k8s-etcd-cluster 
  --initial-cluster=etcd1=https://10.6.0.140:2380,etcd2=https://10.6.0.187:2380,etcd3=https://10.6.0.188:2380 
  --initial-cluster-state=new 
  --data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

启动 etcd

分别启动 所有节点的 etcd 服务

systemctl enable etcd

systemctl start etcd

systemctl status etcd
# 如果报错 请使用
journalctl -f -t etcd  和 journalctl -u etcd 来定位问题

验证 etcd 集群状态

查看 etcd 集群状态:

etcdctl --endpoints=https://10.6.0.140:2379 
        --cert-file=/etc/kubernetes/ssl/etcd.pem 
        --ca-file=/etc/kubernetes/ssl/ca.pem 
        --key-file=/etc/kubernetes/ssl/etcd-key.pem 
        cluster-health

member 29262d49176888f5 is healthy: got healthy result from https://10.6.0.188:2379
member d4ba1a2871bfa2b0 is healthy: got healthy result from https://10.6.0.140:2379
member eca58ebdf44f63b6 is healthy: got healthy result from https://10.6.0.187:2379
cluster is healthy

查看 etcd 集群成员:

etcdctl --endpoints=https://10.6.0.140:2379 
        --cert-file=/etc/kubernetes/ssl/etcd.pem 
        --ca-file=/etc/kubernetes/ssl/ca.pem 
        --key-file=/etc/kubernetes/ssl/etcd-key.pem 
        member list


29262d49176888f5: name=etcd3 peerURLs=https://10.6.0.188:2380 clientURLs=https://10.6.0.188:2379 isLeader=false
d4ba1a2871bfa2b0: name=etcd1 peerURLs=https://10.6.0.140:2380 clientURLs=https://10.6.0.140:2379 isLeader=true
eca58ebdf44f63b6: name=etcd2 peerURLs=https://10.6.0.187:2380 clientURLs=https://10.6.0.187:2379 isLeader=false

安装 kubectl 工具

Master 端

# 首先安装 kubectl

wget https://dl.k8s.io/v1.7.3/kubernetes-client-linux-amd64.tar.gz

tar -xzvf kubernetes-client-linux-amd64.tar.gz

cp kubernetes/client/bin/* /usr/local/bin/

chmod a+x /usr/local/bin/kube*


# 验证安装

kubectl version
Client Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.3", GitCommit:"2c2fe6e8278a5db2d15a013987b53968c743f2a1", GitTreeState:"clean", BuildDate:"2017-08-03T07:00:21Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}
The connection to the server localhost:8080 was refused - did you specify the right host or port?

创建 admin 证书

kubectl 与 kube-apiserver 的安全端口通信,需要为安全通信提供 TLS 证书和秘钥。

cd /opt/ssl/

vi admin-csr.json


{
  "CN": "admin",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "ShenZhen",
      "L": "ShenZhen",
      "O": "system:masters",
      "OU": "System"
    }
  ]
}
# 生成 admin 证书和私钥
cd /opt/ssl/

/opt/local/cfssl/cfssl gencert -ca=/etc/kubernetes/ssl/ca.pem 
  -ca-key=/etc/kubernetes/ssl/ca-key.pem 
  -config=/etc/kubernetes/ssl/config.json 
  -profile=kubernetes admin-csr.json | /opt/local/cfssl/cfssljson -bare admin


# 查看生成

[root@k8s-master-1 ssl]# ls admin*
admin.csr  admin-csr.json  admin-key.pem  admin.pem

cp admin*.pem /etc/kubernetes/ssl/

scp admin*.pem 10.6.0.187:/etc/kubernetes/ssl/

scp admin*.pem 10.6.0.188:/etc/kubernetes/ssl/

配置 kubectl kubeconfig 文件

server 配置为 本机IP 各自连接本机的 Api

# 配置 kubernetes 集群

kubectl config set-cluster kubernetes 
  --certificate-authority=/etc/kubernetes/ssl/ca.pem 
  --embed-certs=true 
  --server=https://10.6.0.140:6443


# 配置 客户端认证

kubectl config set-credentials admin 
  --client-certificate=/etc/kubernetes/ssl/admin.pem 
  --embed-certs=true 
  --client-key=/etc/kubernetes/ssl/admin-key.pem



kubectl config set-context kubernetes 
  --cluster=kubernetes 
  --user=admin


kubectl config use-context kubernetes

kubectl config 文件

# kubeconfig 文件在 如下:

/root/.kube

部署 Kubernetes Master 节点

Master 需要部署 kube-apiserver , kube-scheduler , kube-controller-manager 这三个组件。

安装 组件

# 从github 上下载版本

cd /tmp

wget https://dl.k8s.io/v1.7.3/kubernetes-server-linux-amd64.tar.gz

tar -xzvf kubernetes-server-linux-amd64.tar.gz

cd kubernetes

cp -r server/bin/{kube-apiserver,kube-controller-manager,kube-scheduler,kubectl,kube-proxy,kubelet} /usr/local/bin/

创建 kubernetes 证书

cd /opt/ssl

vi kubernetes-csr.json

{
  "CN": "kubernetes",
  "hosts": [
    "127.0.0.1",
    "10.6.0.140",
    "10.6.0.187",
    "10.6.0.188",
    "10.254.0.1",
    "kubernetes",
    "kubernetes.default",
    "kubernetes.default.svc",
    "kubernetes.default.svc.cluster",
    "kubernetes.default.svc.cluster.local"
  ],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "ShenZhen",
      "L": "ShenZhen",
      "O": "k8s",
      "OU": "System"
    }
  ]
}


## 这里 hosts 字段中 三个 IP 分别为 127.0.0.1 本机, 10.6.0.140 和 10.6.0.187 为 Master 的IP,多个Master需要写多个 10.254.0.1 为 kubernetes SVC 的 IP, 一般是 部署网络的第一个IP , 如: 10.254.0.1 , 在启动完成后,我们使用   kubectl get svc , 就可以查看到

生成 kubernetes 证书和私钥

/opt/local/cfssl/cfssl gencert -ca=/etc/kubernetes/ssl/ca.pem 
  -ca-key=/etc/kubernetes/ssl/ca-key.pem 
  -config=/etc/kubernetes/ssl/config.json 
  -profile=kubernetes kubernetes-csr.json | /opt/local/cfssl/cfssljson -bare kubernetes

# 查看生成

[root@k8s-master-1 ssl]# ls -lt kubernetes*
-rw-r--r-- 1 root root 1245 7月   4 11:25 kubernetes.csr
-rw------- 1 root root 1679 7月   4 11:25 kubernetes-key.pem
-rw-r--r-- 1 root root 1619 7月   4 11:25 kubernetes.pem
-rw-r--r-- 1 root root  436 7月   4 11:23 kubernetes-csr.json


# 拷贝到目录
cp -r kubernetes* /etc/kubernetes/ssl/

scp -r kubernetes* 10.6.0.187:/etc/kubernetes/ssl/

scp -r kubernetes* 10.6.0.188:/etc/kubernetes/ssl/

配置 kube-apiserver

kubelet 首次启动时向 kube-apiserver 发送 TLS Bootstrapping 请求,kube-apiserver 验证 kubelet 请求中的 token 是否与它配置的 token 一致,如果一致则自动为 kubelet生成证书和秘钥。

# 生成 token

[root@k8s-master-1 ssl]# head -c 16 /dev/urandom | od -An -t x | tr -d ' '
10b459a82af1e16663f25061372fdab4


# 创建 token.csv 文件

cd /opt/ssl

vi token.csv

10b459a82af1e16663f25061372fdab4,kubelet-bootstrap,10001,"system:kubelet-bootstrap"


# 拷贝

cp token.csv /etc/kubernetes/

scp token.csv 10.6.0.187:/etc/kubernetes/

scp token.csv 10.6.0.188:/etc/kubernetes/

创建 kube-apiserver.service 文件

# 自定义 系统 service 文件一般存于 /etc/systemd/system/ 下
# 配置为 各自的本地 IP

vi /etc/systemd/system/kube-apiserver.service

[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
User=root
ExecStart=/usr/local/bin/kube-apiserver 
  --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota 
  --advertise-address=10.6.0.140 
  --allow-privileged=true 
  --apiserver-count=3 
  --audit-log-maxage=30 
  --audit-log-maxbackup=3 
  --audit-log-maxsize=100 
  --audit-log-path=/var/lib/audit.log 
  --authorization-mode=RBAC 
  --bind-address=10.6.0.140 
  --client-ca-file=/etc/kubernetes/ssl/ca.pem 
  --enable-swagger-ui=true 
  --etcd-cafile=/etc/kubernetes/ssl/ca.pem 
  --etcd-certfile=/etc/kubernetes/ssl/etcd.pem 
  --etcd-keyfile=/etc/kubernetes/ssl/etcd-key.pem 
  --etcd-servers=https://10.6.0.140:2379,https://10.6.0.187:2379,https://10.6.0.188:2379 
  --event-ttl=1h 
  --kubelet-https=true 
  --insecure-bind-address=10.6.0.140 
  --runtime-config=rbac.authorization.k8s.io/v1alpha1 
  --service-account-key-file=/etc/kubernetes/ssl/ca-key.pem 
  --service-cluster-ip-range=10.254.0.0/16 
  --service-node-port-range=30000-32000 
  --tls-cert-file=/etc/kubernetes/ssl/kubernetes.pem 
  --tls-private-key-file=/etc/kubernetes/ssl/kubernetes-key.pem 
  --experimental-bootstrap-token-auth 
  --token-auth-file=/etc/kubernetes/token.csv 
  --v=2
Restart=on-failure
RestartSec=5
Type=notify
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
# 这里面要注意的是 --service-node-port-range=30000-32000
# 这个地方是 映射外部端口时 的端口范围,随机映射也在这个范围内映射,指定映射端口必须也在这个范围内。

启动 kube-apiserver

systemctl daemon-reload
systemctl enable kube-apiserver
systemctl start kube-apiserver
systemctl status kube-apiserver

配置 kube-controller-manager

master 配置为 各自 本地 IP

# 创建 kube-controller-manager.service 文件

vi /etc/systemd/system/kube-controller-manager.service


[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/GoogleCloudPlatform/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-controller-manager 
  --address=127.0.0.1 
  --master=http://10.6.0.140:8080 
  --allocate-node-cidrs=true 
  --service-cluster-ip-range=10.254.0.0/16 
  --cluster-cidr=10.233.0.0/16 
  --cluster-name=kubernetes 
  --cluster-signing-cert-file=/etc/kubernetes/ssl/ca.pem 
  --cluster-signing-key-file=/etc/kubernetes/ssl/ca-key.pem 
  --service-account-private-key-file=/etc/kubernetes/ssl/ca-key.pem 
  --root-ca-file=/etc/kubernetes/ssl/ca.pem 
  --leader-elect=true 
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

启动 kube-controller-manager

systemctl daemon-reload
systemctl enable kube-controller-manager
systemctl start kube-controller-manager
systemctl status kube-controller-manager

配置 kube-scheduler

master 配置为 各自 本地 IP

# 创建 kube-cheduler.service 文件

vi /etc/systemd/system/kube-scheduler.service


[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/GoogleCloudPlatform/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-scheduler 
  --address=127.0.0.1 
  --master=http://10.6.0.140:8080 
  --leader-elect=true 
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

启动 kube-scheduler

systemctl daemon-reload
systemctl enable kube-scheduler
systemctl start kube-scheduler
systemctl status kube-scheduler

验证 Master 节点

[root@k8s-master-1 ~]# kubectl get componentstatuses
NAME                 STATUS    MESSAGE              ERROR
scheduler            Healthy   ok                   
controller-manager   Healthy   ok                   
etcd-0               Healthy   {"health": "true"}   
etcd-2               Healthy   {"health": "true"}   
etcd-1               Healthy   {"health": "true"} 



[root@k8s-master-2 ~]# kubectl get componentstatuses
NAME                 STATUS    MESSAGE              ERROR
controller-manager   Healthy   ok                   
scheduler            Healthy   ok                   
etcd-2               Healthy   {"health": "true"}   
etcd-0               Healthy   {"health": "true"}   
etcd-1               Healthy   {"health": "true"}  



[root@k8s-master-3 ~]# kubectl get componentstatuses
NAME                 STATUS    MESSAGE              ERROR
scheduler            Healthy   ok                   
controller-manager   Healthy   ok                   
etcd-0               Healthy   {"health": "true"}   
etcd-1               Healthy   {"health": "true"}   
etcd-2               Healthy   {"health": "true"}   

部署 Master Node 部分

Node 部分 需要部署的组件有 docker calico kubectl kubelet kube-proxy 这几个组件。

配置 kubelet

kubelet 启动时向 kube-apiserver 发送 TLS bootstrapping 请求,需要先将 bootstrap token 文件中的 kubelet-bootstrap 用户赋予 system:node-bootstrapper 角色,然后 kubelet 才有权限创建认证请求(certificatesigningrequests)。

# 先创建认证请求
# user 为 master 中 token.csv 文件里配置的用户
# 只需创建一次就可以

kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --user=kubelet-bootstrap

创建 kubelet kubeconfig 文件

server 配置为 master 本机 IP

# 配置集群

kubectl config set-cluster kubernetes 
  --certificate-authority=/etc/kubernetes/ssl/ca.pem 
  --embed-certs=true 
  --server=https://10.6.0.140:6443 
  --kubeconfig=bootstrap.kubeconfig

# 配置客户端认证

kubectl config set-credentials kubelet-bootstrap 
  --token=10b459a82af1e16663f25061372fdab4 
  --kubeconfig=bootstrap.kubeconfig


# 配置关联

kubectl config set-context default 
  --cluster=kubernetes 
  --user=kubelet-bootstrap 
  --kubeconfig=bootstrap.kubeconfig


# 配置默认关联
kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

# 拷贝生成的 bootstrap.kubeconfig 文件

mv bootstrap.kubeconfig /etc/kubernetes/

创建 kubelet.service 文件

# 创建 kubelet 目录

> 配置为 node 本机 IP

mkdir /var/lib/kubelet

vi /etc/systemd/system/kubelet.service


[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=docker.service
Requires=docker.service

[Service]
WorkingDirectory=/var/lib/kubelet
ExecStart=/usr/local/bin/kubelet 
  --address=10.6.0.140 
  --hostname-override=10.6.0.140 
  --pod-infra-container-image=jicki/pause-amd64:3.0 
  --experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig 
  --kubeconfig=/etc/kubernetes/kubelet.kubeconfig 
  --require-kubeconfig 
  --cert-dir=/etc/kubernetes/ssl 
  --cluster_dns=10.254.0.2 
  --cluster_domain=cluster.local. 
  --hairpin-mode promiscuous-bridge 
  --allow-privileged=true 
  --serialize-image-pulls=false 
  --logtostderr=true 
  --v=2
ExecStopPost=/sbin/iptables -A INPUT -s 10.0.0.0/8 -p tcp --dport 4194 -j ACCEPT
ExecStopPost=/sbin/iptables -A INPUT -s 172.16.0.0/12 -p tcp --dport 4194 -j ACCEPT
ExecStopPost=/sbin/iptables -A INPUT -s 192.168.0.0/16 -p tcp --dport 4194 -j ACCEPT
ExecStopPost=/sbin/iptables -A INPUT -p tcp --dport 4194 -j DROP
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
# 如上配置:
10.6.0.140       为本机的IP
10.254.0.2       预分配的 dns 地址
cluster.local.   为 kubernetes 集群的 domain
jicki/pause-amd64:3.0  这个是 pod 的基础镜像,既 gcr 的 gcr.io/google_containers/pause-amd64:3.0 镜像, 下载下来修改为自己的仓库中的比较快。

启动 kubelet

systemctl daemon-reload
systemctl enable kubelet
systemctl start kubelet
systemctl status kubelet

配置 TLS 认证

# 查看 csr 的名称

[root@k8s-master-1 ~]# kubectl get csr
NAME                                                   AGE       REQUESTOR           CONDITION
node-csr-LkH2ZX9b2kACKmkNbp9PnK6BYAa5fMeh7nWtrGCipYc   58s       kubelet-bootstrap   Pending
node-csr-cxXvnzvukInZrSXT1EJTFaDzERFsuwsR2hCcgWyYZ2o   1m        kubelet-bootstrap   Pending
node-csr-jcQdD_haTRkPMTXwcHeyjQZUt2lb1S4rDeTgKUeQwgM   1m        kubelet-bootstrap   Pending


# 增加 认证

[root@k8s-master-1 ~]# kubectl certificate approve node-csr-LkH2ZX9b2kACKmkNbp9PnK6BYAa5fMeh7nWtrGCipYc
certificatesigningrequest "node-csr-LkH2ZX9b2kACKmkNbp9PnK6BYAa5fMeh7nWtrGCipYc" approved
[root@k8s-master-1 ~]# 
[root@k8s-master-1 ~]# kubectl certificate approve node-csr-cxXvnzvukInZrSXT1EJTFaDzERFsuwsR2hCcgWyYZ2o
certificatesigningrequest "node-csr-cxXvnzvukInZrSXT1EJTFaDzERFsuwsR2hCcgWyYZ2o" approved
[root@k8s-master-1 ~]# 
[root@k8s-master-1 ~]# kubectl certificate approve node-csr-jcQdD_haTRkPMTXwcHeyjQZUt2lb1S4rDeTgKUeQwgM
certificatesigningrequest "node-csr-jcQdD_haTRkPMTXwcHeyjQZUt2lb1S4rDeTgKUeQwgM" approved

验证 nodes

[root@k8s-master-1 ~]# kubectl get nodes
NAME         STATUS    AGE       VERSION
10.6.0.140   Ready     27s       v1.7.3
10.6.0.187   Ready     20s       v1.7.3
10.6.0.188   Ready     37s       v1.7.3


# 成功以后会自动生成配置文件与密钥

# 配置文件

ls /etc/kubernetes/kubelet.kubeconfig   
/etc/kubernetes/kubelet.kubeconfig


# 密钥文件

ls /etc/kubernetes/ssl/kubelet*
/etc/kubernetes/ssl/kubelet-client.crt  /etc/kubernetes/ssl/kubelet.crt
/etc/kubernetes/ssl/kubelet-client.key  /etc/kubernetes/ssl/kubelet.key

配置 kube-proxy

创建 kube-proxy 证书

# 证书方面由于我们node端没有装 cfssl
# 我们回到 master 端 机器 去配置证书,然后拷贝过来

[root@k8s-master-1 ~]# cd /opt/ssl


vi kube-proxy-csr.json

{
  "CN": "system:kube-proxy",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "ShenZhen",
      "L": "ShenZhen",
      "O": "k8s",
      "OU": "System"
    }
  ]
}

生成 kube-proxy 证书和私钥

/opt/local/cfssl/cfssl gencert -ca=/etc/kubernetes/ssl/ca.pem 
  -ca-key=/etc/kubernetes/ssl/ca-key.pem 
  -config=/etc/kubernetes/ssl/config.json 
  -profile=kubernetes  kube-proxy-csr.json | /opt/local/cfssl/cfssljson -bare kube-proxy

# 查看生成
ls kube-proxy*
kube-proxy.csr  kube-proxy-csr.json  kube-proxy-key.pem  kube-proxy.pem

# 拷贝到目录
cp kube-proxy*.pem /etc/kubernetes/ssl/

scp kube-proxy*.pem 10.6.0.187:/etc/kubernetes/ssl/

scp kube-proxy*.pem 10.6.0.188:/etc/kubernetes/ssl/

创建 kube-proxy kubeconfig 文件

server 配置为各自 本机IP

# 配置集群

kubectl config set-cluster kubernetes 
  --certificate-authority=/etc/kubernetes/ssl/ca.pem 
  --embed-certs=true 
  --server=https://10.6.0.140:6443 
  --kubeconfig=kube-proxy.kubeconfig


# 配置客户端认证

kubectl config set-credentials kube-proxy 
  --client-certificate=/etc/kubernetes/ssl/kube-proxy.pem 
  --client-key=/etc/kubernetes/ssl/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

# 拷贝到目录
mv kube-proxy.kubeconfig /etc/kubernetes/

创建 kube-proxy.service 文件

配置为 各自的 IP

# 创建 kube-proxy 目录

mkdir -p /var/lib/kube-proxy


vi /etc/systemd/system/kube-proxy.service

[Unit]
Description=Kubernetes Kube-Proxy Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
WorkingDirectory=/var/lib/kube-proxy
ExecStart=/usr/local/bin/kube-proxy 
  --bind-address=10.6.0.140 
  --hostname-override=10.6.0.140 
  --cluster-cidr=10.254.0.0/16 
  --kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig 
  --logtostderr=true 
  --v=2
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

启动 kube-proxy

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

部署 Node 节点

Node 节点 基于 Nginx 负载 API 做 Master HA

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

wget https://dl.k8s.io/v1.7.3/kubernetes-server-linux-amd64.tar.gz

tar -xzvf kubernetes-server-linux-amd64.tar.gz

cd kubernetes

cp -r server/bin/{kube-proxy,kubelet} /usr/local/bin/
# kubelet

# 首先 创建 kubelet kubeconfig 文件


kubectl config set-cluster kubernetes 
  --certificate-authority=/etc/kubernetes/ssl/ca.pem 
  --embed-certs=true 
  --server=https://127.0.0.1:6443 
  --kubeconfig=bootstrap.kubeconfig


# 配置客户端认证

kubectl config set-credentials kubelet-bootstrap 
  --token=10b459a82af1e16663f25061372fdab4 
  --kubeconfig=bootstrap.kubeconfig


# 配置关联

kubectl config set-context default 
  --cluster=kubernetes 
  --user=kubelet-bootstrap 
  --kubeconfig=bootstrap.kubeconfig


# 配置默认关联
kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

# 拷贝生成的 bootstrap.kubeconfig 文件

mv bootstrap.kubeconfig /etc/kubernetes/
# 创建 kube-proxy kubeconfig 文件


kubectl config set-cluster kubernetes 
  --certificate-authority=/etc/kubernetes/ssl/ca.pem 
  --embed-certs=true 
  --server=https://127.0.0.1:6443 
  --kubeconfig=kube-proxy.kubeconfig


# 配置客户端认证

kubectl config set-credentials kube-proxy 
  --client-certificate=/etc/kubernetes/ssl/kube-proxy.pem 
  --client-key=/etc/kubernetes/ssl/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

# 拷贝到目录
mv kube-proxy.kubeconfig /etc/kubernetes/

创建Nginx 代理

在每个 node 都必须创建一个 Nginx 代理, 这里特别注意, 当 Master 也做为 Node 的时候 不需要配置 Nginx-proxy

# 创建配置目录
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 10.6.0.140:6443;
        server 10.6.0.187:6443;
        server 10.6.0.188:6443;
    }

    server {
        listen        0.0.0.0:6443;
        proxy_pass    kube_apiserver;
        proxy_timeout 10m;
        proxy_connect_timeout 1s;
    }
}
EOF
# 配置 Nginx 基于 docker 进程,然后配置 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 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

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


# 重启 Node 的 kubelet 与 kube-proxy

systemctl restart kubelet
systemctl status kubelet

systemctl restart kube-proxy
systemctl status kube-proxy

Master 配置 TLS 认证

# 查看 csr 的名称

[root@k8s-master-1 ~]# kubectl get csr
NAME                                                   AGE       REQUESTOR           CONDITION
node-csr-LkH2ZX9b2kACKmkNbp9PnK6BYAa5fMeh7nWtrGCipYc   58s       kubelet-bootstrap   Pending
node-csr-cxXvnzvukInZrSXT1EJTFaDzERFsuwsR2hCcgWyYZ2o   1m        kubelet-bootstrap   Pending
node-csr-jcQdD_haTRkPMTXwcHeyjQZUt2lb1S4rDeTgKUeQwgM   1m        kubelet-bootstrap   Pending


# 增加 认证

[root@k8s-master-1 ~]# kubectl certificate approve node-csr-LkH2ZX9b2kACKmkNbp9PnK6BYAa5fMeh7nWtrGCipYc
certificatesigningrequest "node-csr-LkH2ZX9b2kACKmkNbp9PnK6BYAa5fMeh7nWtrGCipYc" approved
[root@k8s-master-1 ~]# 
[root@k8s-master-1 ~]# kubectl certificate approve node-csr-cxXvnzvukInZrSXT1EJTFaDzERFsuwsR2hCcgWyYZ2o
certificatesigningrequest "node-csr-cxXvnzvukInZrSXT1EJTFaDzERFsuwsR2hCcgWyYZ2o" approved
[root@k8s-master-1 ~]# 
[root@k8s-master-1 ~]# kubectl certificate approve node-csr-jcQdD_haTRkPMTXwcHeyjQZUt2lb1S4rDeTgKUeQwgM
certificatesigningrequest "node-csr-jcQdD_haTRkPMTXwcHeyjQZUt2lb1S4rDeTgKUeQwgM" approved

安装 docker

# 导入 yum 源

# 安装 yum-config-manager

yum -y install yum-utils

# 导入
yum-config-manager 
    --add-repo 
    https://download.docker.com/linux/centos/docker-ce.repo


# 更新 repo
yum makecache

# 安装

yum install docker-ce -y

更改docker 配置

# 修改配置

vi /usr/lib/systemd/system/docker.service

[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target

[Service]
Type=notify
ExecStart=/usr/bin/dockerd $DOCKER_NETWORK_OPTIONS $DOCKER_OPTS $DOCKER_DNS_OPTIONS
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TimeoutStartSec=0
Delegate=yes
KillMode=process
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s

[Install]
WantedBy=multi-user.target
# 修改其他配置

mkdir -p /usr/lib/systemd/system/docker.service.d/


cat >> /usr/lib/systemd/system/docker.service.d/docker-options.conf << EOF
[Service]
Environment="DOCKER_OPTS=--insecure-registry=10.254.0.0/16 --graph=/opt/docker --registry-mirror=http://b438f72b.m.daocloud.io --iptables=false"
EOF
# 重新读取配置,启动 docker 
systemctl daemon-reload
systemctl start docker
systemctl enable docker

Calico 网络

修改 kubelet.service

vi /etc/systemd/system/kubelet.service

# 增加 如下配置

  --network-plugin=cni 


# 重新加载配置
systemctl daemon-reload
systemctl restart kubelet.service
systemctl status kubelet.service

修改 kube-proxy.service

vi /etc/systemd/system/kube-proxy.service

# 增加如下配置:
    --masquerade-all 

# 重新加载配置
systemctl daemon-reload
systemctl restart kube-proxy.service
systemctl status kube-proxy.service

安装 Calico

官网地址 http://docs.projectcalico.org/v2.3/getting-started/kubernetes/installation/hosted/hosted

# 下载 yaml 文件

wget http://docs.projectcalico.org/v2.3/getting-started/kubernetes/installation/hosted/calico.yaml

wget http://docs.projectcalico.org/v2.3/getting-started/kubernetes/installation/rbac.yaml


# 下载 镜像

# 国外镜像 有墙
quay.io/calico/node:v1.3.0
quay.io/calico/cni:v1.9.1
quay.io/calico/kube-policy-controller:v0.6.0


# 国内镜像
jicki/node:v1.3.0
jicki/cni:v1.9.1
jicki/kube-policy-controller:v0.6.0

配置 calico

vi calico.yaml

# 注意修改如下选项:


  etcd_endpoints: "https://10.6.0.140:2379,https://10.6.0.187:2379,https://10.6.0.188:2379"

    etcd_ca: "/calico-secrets/etcd-ca"  
    etcd_cert: "/calico-secrets/etcd-cert"
    etcd_key: "/calico-secrets/etcd-key"  


# 这里面要写入 base64 的信息



data:
  etcd-key: (cat /etc/kubernetes/ssl/etcd-key.pem | base64 | tr -d 'n')
  etcd-cert: (cat /etc/kubernetes/ssl/etcd.pem | base64 | tr -d 'n')
  etcd-ca: (cat /etc/kubernetes/ssl/ca.pem | base64 | tr -d 'n')


    - name: CALICO_IPV4POOL_CIDR
      value: "10.233.0.0/16"

导入 yaml 文件

[root@k8s-master-1 ~]# kubectl apply -f calico.yaml 
configmap "calico-config" created
secret "calico-etcd-secrets" created
daemonset "calico-node" created
deployment "calico-policy-controller" created
serviceaccount "calico-policy-controller" created
serviceaccount "calico-node" created


[root@k8s-master-1 ~]# kubectl apply -f rbac.yaml
clusterrole "calico-policy-controller" created
clusterrolebinding "calico-policy-controller" created
clusterrole "calico-node" created
clusterrolebinding "calico-node" created

验证 Calico

[root@k8s-master-1 ~]# kubectl get pods -n kube-system 
NAME                                       READY     STATUS    RESTARTS   AGE
calico-node-2dsq4                          2/2       Running   0          6m
calico-node-9ktvk                          2/2       Running   0          6m
calico-node-gwmx5                          2/2       Running   0          6m
calico-policy-controller-458850194-pn65p   1/1       Running   0          6m

安装 Calicoctl

cd /usr/local/bin/

wget -c  https://github.com/projectcalico/calicoctl/releases/download/v1.3.0/calicoctl

chmod +x calicoctl



## 创建 calicoctl 配置文件

# 配置文件, 在 安装了 calico 网络的 机器下

mkdir /etc/calico

vi /etc/calico/calicoctl.cfg


apiVersion: v1
kind: calicoApiConfig
metadata:
spec:
  datastoreType: "etcdv2"
  etcdEndpoints: "https://10.6.0.140:2379,https://10.6.0.187:2379,https://10.6.0.188:2379"
  etcdKeyFile: "/etc/kubernetes/ssl/etcd-key.pem"
  etcdCertFile: "/etc/kubernetes/ssl/etcd.pem"
  etcdCACertFile: "/etc/kubernetes/ssl/ca.pem"




# 查看 calico 状态

[root@k8s-master-1 ~]# calicoctl node status
Calico process is running.

IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS |     PEER TYPE     | STATE |  SINCE   |    INFO     |
+--------------+-------------------+-------+----------+-------------+
| 10.6.0.188   | node-to-node mesh | up    | 10:11:59 | Established |
| 10.6.0.187   | node-to-node mesh | up    | 10:16:32 | Established |
+--------------+-------------------+-------+----------+-------------+

IPv6 BGP status
No IPv6 peers found.

测试集群

# 创建一个 nginx deplyment

apiVersion: extensions/v1beta1 
kind: Deployment 
metadata: 
  name: nginx-dm
spec: 
  replicas: 2
  template: 
    metadata: 
      labels: 
        name: nginx 
    spec: 
      containers: 
        - name: nginx 
          image: nginx:alpine 
          imagePullPolicy: IfNotPresent
          ports: 
            - containerPort: 80

---

apiVersion: v1 
kind: Service
metadata: 
  name: nginx-svc 
spec: 
  ports: 
    - port: 80
      targetPort: 80
      protocol: TCP 
  selector: 
    name: nginx
[root@k8s-master-1 ~]# kubectl get pods
NAME                        READY     STATUS    RESTARTS   AGE
nginx-dm-2214564181-lxff5   1/1       Running   0          14m
nginx-dm-2214564181-qm1bp   1/1       Running   0          14m


[root@k8s-master-1 ~]# kubectl get deployment
NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-dm   2         2         2            2           14m


[root@k8s-master-1 ~]# kubectl get svc
NAME         CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   10.254.0.1      <none>        443/TCP   4h
nginx-svc    10.254.129.54   <none>        80/TCP    15m
# 在 node 里 curl

[root@k8s-node-1 ~]# curl 10.254.129.54
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

配置 KubeDNS

官方 github yaml 相关 https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/dns

下载镜像

# 官方镜像
gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.4
gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.4
gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.4


# 我的镜像
jicki/k8s-dns-sidecar-amd64:1.14.4
jicki/k8s-dns-kube-dns-amd64:1.14.4
jicki/k8s-dns-dnsmasq-nanny-amd64:1.14.4

下载 yaml 文件

curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kubedns-cm.yaml


curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kubedns-sa.yaml


curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kubedns-controller.yaml.base


curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kubedns-svc.yaml.base


# 修改后缀

mv kubedns-controller.yaml.base kubedns-controller.yaml

mv kubedns-svc.yaml.base kubedns-svc.yaml

系统预定义的 RoleBinding

预定义的 RoleBinding system:kube-dns 将 kube-system 命名空间的 kube-dns ServiceAccount 与 system:kube-dns Role 绑定, 该 Role 具有访问 kube-apiserver DNS 相关 API 的权限;

[root@k8s-master-1 kubedns]# kubectl get clusterrolebindings system:kube-dns -o yaml
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  creationTimestamp: 2017-07-04T04:15:13Z
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:kube-dns
  resourceVersion: "106"
  selfLink: /apis/rbac.authorization.k8s.io/v1beta1/clusterrolebindings/system%3Akube-dns
  uid: 60c1e0e1-606f-11e7-b212-d4ae52d1f0c9
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:kube-dns
subjects:
- kind: ServiceAccount
  name: kube-dns
  namespace: kube-system

修改 kubedns-svc.yaml

# kubedns-svc.yaml 中 clusterIP: __PILLAR__DNS__SERVER__ 修改为我们之前定义的 dns IP 10.254.0.2

cat kubedns-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 10.254.0.2
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP

修改 kubedns-controller.yaml

1. # 修改 --domain=__PILLAR__DNS__DOMAIN__.   为 我们之前 预定的 domain 名称 --domain=cluster.local.

2. # 修改 --server=/__PILLAR__DNS__DOMAIN__/127.0.0.1#10053  中 domain 为我们之前预定的 --server=/cluster.local./127.0.0.1#10053

3. # 修改 --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.__PILLAR__DNS__DOMAIN__, 中的 domain 为我们之前预定的  --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.cluster.local.,

4. # 修改 --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.__PILLAR__DNS__DOMAIN__,  中的 domain 为我们之前预定的  --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.cluster.local.,

导入 yaml 文件

# 替换所有的 images

sed -i 's/gcr.io/google_containers/jicki/g' *

# 导入

[root@k8s-master-1 kubedns]# kubectl create -f .
configmap "kube-dns" created
deployment "kube-dns" created
serviceaccount "kube-dns" created
service "kube-dns" created

查看 kubedns 服务

[root@k8s-master-1 kubedns]# kubectl get all --namespace=kube-system
NAME                                          READY     STATUS    RESTARTS   AGE
po/calico-node-2dsq4                          2/2       Running   0          15h
po/calico-node-9ktvk                          2/2       Running   0          15h
po/calico-node-gwmx5                          2/2       Running   0          15h
po/calico-policy-controller-458850194-pn65p   1/1       Running   0          15h
po/kube-dns-1511229508-jxkvs                  3/3       Running   0          4m

NAME           CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
svc/kube-dns   10.254.0.2   <none>        53/UDP,53/TCP   4m

NAME                              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/calico-policy-controller   1         1         1            1           15h
deploy/kube-dns                   1         1         1            1           4m

NAME                                    DESIRED   CURRENT   READY     AGE
rs/calico-policy-controller-458850194   1         1         1         15h
rs/kube-dns-1511229508                  1         1         1         4m

验证 dns 服务

在验证 dns 之前,在 dns 未部署之前创建的 pod 与 deployment 等,都必须删除,重新部署,否则无法解析

# 导入之前的 nginx-dm yaml文件

[root@k8s-master-1 ~]# kubectl get pods -o wide
NAME                        READY     STATUS    RESTARTS   AGE       IP              NODE
nginx-dm-2214564181-0ctcx   1/1       Running   0          27s       10.233.168.1    10.6.0.188
nginx-dm-2214564181-brz79   1/1       Running   0          3m        10.233.196.2    10.6.0.140
nginx-dm-2214564181-z8whk   1/1       Running   0          3m        10.233.182.65   10.6.0.187

[root@k8s-master-1 ~]# kubectl get svc -o wide
NAME         CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE       SELECTOR
kubernetes   10.254.0.1     <none>        443/TCP   16h       <none>
nginx-svc    10.254.140.2   <none>        80/TCP    3m        name=nginx


# 创建一个 pods 来测试一下 nameserver

apiVersion: v1
kind: Pod
metadata:
  name: alpine
spec:
  containers:
  - name: alpine
    image: alpine
    command:
    - sh
    - -c
    - while true; do sleep 1; done



# 查看 pods
[root@k8s-master-1 ~]# kubectl get pods
NAME                        READY     STATUS    RESTARTS   AGE
alpine                      1/1       Running   0          5s
nginx-dm-2214564181-0ctcx   1/1       Running   0          7m
nginx-dm-2214564181-brz79   1/1       Running   0          10m
nginx-dm-2214564181-z8whk   1/1       Running   0          10m



# 测试

[root@k8s-master-1 ~]# kubectl exec -it alpine nslookup nginx-svc
nslookup: can't resolve '(null)': Name does not resolve

Name:      nginx-svc
Address 1: 10.254.140.2 nginx-svc.default.svc.cluster.local


[root@k8s-master-1 ~]# kubectl exec -it alpine ping nginx-svc    
PING nginx-svc (10.254.140.2): 56 data bytes

部署 Ingress 与 Dashboard

部署 dashboard && heapster

官方 dashboard 的github https://github.com/kubernetes/dashboard

下载 dashboard 镜像

# 官方镜像
gcr.io/google_containers/kubernetes-dashboard-amd64:v1.6.3

# 国内镜像
jicki/kubernetes-dashboard-amd64:v1.6.3

下载 yaml 文件

curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dashboard/dashboard-controller.yaml


curl -O https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dashboard/dashboard-service.yaml



# 因为开启了 RBAC 所以这里需要创建一个 RBAC 认证

vi dashboard-rbac.yaml


apiVersion: v1
kind: ServiceAccount
metadata:
  name: dashboard
  namespace: kube-system

---

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1alpha1
metadata:
  name: dashboard
subjects:
  - kind: ServiceAccount
    name: dashboard
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

下载 heapster 镜像

# 官方文件
gcr.io/google_containers/heapster-amd64:v1.3.0
gcr.io/google_containers/heapster-influxdb-amd64:v1.1.1


# 本地文件
jicki/heapster-amd64:v1.3.0
jicki/heapster-influxdb-amd64:v1.1.1

下载 heapster 文件

官方网站 https://github.com/kubernetes/heapster/blob/master/docs/influxdb.md

curl -O https://raw.githubusercontent.com/kubernetes/heapster/master/deploy/kube-config/influxdb/influxdb.yaml

curl -O https://raw.githubusercontent.com/kubernetes/heapster/master/deploy/kube-config/influxdb/heapster.yaml

curl -O https://raw.githubusercontent.com/kubernetes/heapster/master/deploy/kube-config/rbac/heapster-rbac.yaml

修改 influxdb 配置

# influxdb.yaml 记录存储data数据

# 如下:

        volumeMounts:
        - mountPath: /data
          name: influxdb-storage
      volumes:
      - name: influxdb-storage
        emptyDir: {}


# volumes 请自行修改为 共享存储 或者 本地目录

导入 yaml

# 替换所有的 images

sed -i 's/gcr.io/google_containers/jicki/g' *




# dashboard-controller.yaml 增加 rbac 授权


# 在第二个 spec 下面 增加

    spec:
      serviceAccountName: dashboard



# 导入文件

[root@k8s-master-1 dashboard]# kubectl apply -f .
deployment "kubernetes-dashboard" created
serviceaccount "dashboard" created
clusterrolebinding "dashboard" created
service "kubernetes-dashboard" created



[root@k8s-master-1 heapster]# kubectl apply -f .
clusterrolebinding "heapster" created
serviceaccount "heapster" created
deployment "heapster" created
service "heapster" created
deployment "monitoring-influxdb" created
service "monitoring-influxdb" created



# 查看 svc 与 pod

[root@k8s-master-1 ~]# kubectl get svc -n kube-system
NAME                  CLUSTER-IP       EXTERNAL-IP   PORT(S)         AGE
heapster              10.254.231.18    <none>        80/TCP          13s
kube-dns              10.254.0.2       <none>        53/UDP,53/TCP   1h
monitoring-influxdb   10.254.240.245   <none>        8086/TCP        13s

部署 Nginx Ingress

Kubernetes 暴露服务的方式目前只有三种:LoadBlancer Service、NodePort Service、Ingress; 什么是 Ingress ? Ingress 就是利用 Nginx Haproxy 等负载均衡工具来暴露 Kubernetes 服务。

官方 Nginx Ingress github https://github.com/kubernetes/ingress/tree/master/examples/deployment/nginx

配置 调度 node

# ingress 有多种方式 1.  deployment 自由调度 replicas
                     2.  daemonset 全局调度 分配到所有node里


#  deployment 自由调度过程中,由于我们需要 约束 controller 调度到指定的 node 中,所以需要对 node 进行 label 标签


# 默认如下:
[root@k8s-master-1 ingress]# kubectl get nodes
NAME         STATUS    AGE       VERSION
10.6.0.140   Ready     18h       v1.7.3
10.6.0.187   Ready     18h       v1.7.3
10.6.0.188   Ready     18h       v1.7.3


# 对 140 与 187 打上 label

[root@k8s-master-1 ingress]# kubectl label nodes 10.6.0.140 ingress=proxy
node "10.6.0.140" labeled
[root@k8s-master-1 ingress]# kubectl label nodes 10.6.0.187 ingress=proxy
node "10.6.0.187" labeled


# 打完标签以后

[root@k8s-master-1 ingress]# kubectl get nodes --show-labels
NAME         STATUS    AGE       VERSION   LABELS
10.6.0.140   Ready     18h       v1.7.3    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,ingress=proxy,kubernetes.io/hostname=10.6.0.140
10.6.0.187   Ready     18h       v1.7.3    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,ingress=proxy,kubernetes.io/hostname=10.6.0.187
10.6.0.188   Ready     18h       v1.7.3    beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=10.6.0.188
# 下载镜像

# 官方镜像
gcr.io/google_containers/defaultbackend:1.0
gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.11

# 国内镜像
jicki/defaultbackend:1.0
jicki/nginx-ingress-controller:0.9.0-beta.11
# 部署 Nginx  backend , Nginx backend 用于统一转发 没有的域名 到指定页面。

curl -O https://raw.githubusercontent.com/kubernetes/ingress/master/examples/deployment/nginx/default-backend.yaml


# 直接导入既可, 这里不需要修改

[root@k8s-master-1 ingress]# kubectl apply -f default-backend.yaml 
deployment "default-http-backend" created
service "default-http-backend" created



# 查看服务
[root@k8s-master-1 ingress]# kubectl get deployment -n kube-system default-http-backend
NAME                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
default-http-backend   1         1         1            1           36s
# 部署 Ingress RBAC 认证

curl -O https://raw.githubusercontent.com/kubernetes/ingress/master/examples/rbac/nginx/nginx-ingress-controller-rbac.yml


# 修改 namespace

sed -i 's/namespace: nginx-ingress/namespace: kube-system/g' nginx-ingress-controller-rbac.yml


# 导入 yaml 文件

[root@k8s-master-1 ingress]# kubectl apply -f nginx-ingress-controller-rbac.yml 
namespace "nginx-ingress" created
serviceaccount "nginx-ingress-serviceaccount" created
clusterrole "nginx-ingress-clusterrole" created
role "nginx-ingress-role" created
rolebinding "nginx-ingress-role-nisa-binding" created
clusterrolebinding "nginx-ingress-clusterrole-nisa-binding" created
# 部署 Ingress Controller 组件

# 下载 yaml 文件

curl -O https://raw.githubusercontent.com/kubernetes/ingress/master/examples/deployment/nginx/nginx-ingress-controller.yaml

# 上面 对 两个 node 打了 label 所以配置 replicas: 2
# 修改 yaml 文件 增加 rbac 认证 , hostNetwork  还有 nodeSelector, 第二个 spec 下 增加。

spec:
  replicas: 2
  ....
    spec:
      hostNetwork: true
      serviceAccountName: nginx-ingress-serviceaccount
      nodeSelector:
        ingress: proxy
    ....
# 导入 yaml 文件

[root@k8s-master-1 ingress]# kubectl apply -f nginx-ingress-controller.yaml
deployment "nginx-ingress-controller" created


# 查看服务,可以看到这两个 pods 被分别调度到 140 与 187 中
[root@k8s-master-1 ingress]# kubectl get pods -n kube-system -o wide
NAME                                       READY     STATUS    RESTARTS   AGE       IP             NODE
nginx-ingress-controller-190167013-9j5kd   1/1       Running   0          45s       10.6.0.140     10.6.0.140
nginx-ingress-controller-190167013-n66qd   1/1       Running   0          45s       10.6.0.187     10.6.0.187
# 查看我们原有的 svc

[root@k8s-master-1 dashboard]# kubectl get svc -n kube-system kubernetes-dashboard
NAME                   CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes-dashboard   10.254.243.198   <none>        80/TCP    21s


# 创建 yaml 文件

vi dashboard-ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: dashboard-ingress
  namespaces: kube-system
spec:
  rules:
  - host: dashboard.jicki.me
    http:
      paths:
      - backend:
          serviceName: kubernetes-dashboard
          servicePort: 80



# 导入 yaml

[root@k8s-master-1 dashboard]# kubectl apply -f dashboard-ingress.yaml 
ingress "dashboard-ingress" created



# 查看 ingress

[root@k8s-master-1 dashboard]# kubectl get ingress -n kube-system -o wide
NAME                HOSTS                ADDRESS                 PORTS     AGE
dashboard-ingress   dashboard.jicki.me   10.6.0.140,10.6.0.187   80        1h


# 测试访问

[root@k8s-master-1 dashboard]# curl -I dashboard.jicki.me
HTTP/1.1 200 OK
Server: nginx/1.13.2
Date: Thu, 06 Jul 2017 06:32:00 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 848
Connection: keep-alive
Accept-Ranges: bytes
Cache-Control: no-store
Last-Modified: Tue, 16 May 2017 12:53:01 GMT

未分类

破坏性测试

破坏性测试,手动重启服务器,查看各服务间的恢复以及问题

[root@k8s-master-1 ~]# kubectl get nodes
NAME         STATUS    AGE       VERSION
10.6.0.140   Ready     20h       v1.7.3
10.6.0.187   Ready     20h       v1.7.3
10.6.0.188   Ready     20h       v1.7.3



[root@k8s-master-1 nginx]# etcdctl --endpoints=https://10.6.0.140:2379 
>         --cert-file=/etc/kubernetes/ssl/etcd.pem 
>         --ca-file=/etc/kubernetes/ssl/ca.pem 
>         --key-file=/etc/kubernetes/ssl/etcd-key.pem 
>         member list
2017-08-08 02:36:38.187672 I | warning: ignoring ServerName for user-provided CA for backwards compatibility is deprecated
29262d49176888f5: name=etcd3 peerURLs=https://10.6.0.188:2380 clientURLs=https://10.6.0.188:2379 isLeader=false
d4ba1a2871bfa2b0: name=etcd1 peerURLs=https://10.6.0.140:2380 clientURLs=https://10.6.0.140:2379 isLeader=true
eca58ebdf44f63b6: name=etcd2 peerURLs=https://10.6.0.187:2380 clientURLs=https://10.6.0.187:2379 isLeader=false


[root@k8s-master-1 ~]# reboot
PolicyKit daemon disconnected from the bus.
We are no longer a registered authentication agent.
# 服务器重启

[root@k8s-master-2 ~]# kubectl get nodes
NAME         STATUS     AGE       VERSION
10.6.0.140   NotReady   20h       v1.7.3
10.6.0.187   Ready      20h       v1.7.3
10.6.0.188   Ready      20h       v1.7.3



[root@k8s-master-2 ~]# etcdctl --endpoints=https://10.6.0.187:2379 
>         --cert-file=/etc/kubernetes/ssl/etcd.pem 
>         --ca-file=/etc/kubernetes/ssl/ca.pem 
>         --key-file=/etc/kubernetes/ssl/etcd-key.pem 
>         member list
2017-08-08 02:34:00.132653 I | warning: ignoring ServerName for user-provided CA for backwards compatibility is deprecated
29262d49176888f5: name=etcd3 peerURLs=https://10.6.0.188:2380 clientURLs=https://10.6.0.188:2379 isLeader=false
d4ba1a2871bfa2b0: name=etcd1 peerURLs=https://10.6.0.140:2380 clientURLs=https://10.6.0.140:2379 isLeader=false
eca58ebdf44f63b6: name=etcd2 peerURLs=https://10.6.0.187:2380 clientURLs=https://10.6.0.187:2379 isLeader=true



# 等待服务器重启以后

[root@k8s-master-2 ~]# kubectl get nodes
NAME         STATUS    AGE       VERSION
10.6.0.140   Ready     20h       v1.7.3
10.6.0.187   Ready     20h       v1.7.3
10.6.0.188   Ready     20h       v1.7.3


[root@k8s-master-2 ~]# kubectl get pods -o wide
NAME                        READY     STATUS    RESTARTS   AGE       IP              NODE
alpine                      1/1       Running   1          4h        10.233.196.8    10.6.0.140
nginx-dm-2214564181-0ctcx   1/1       Running   0          4h        10.233.168.4    10.6.0.188
nginx-dm-2214564181-brz79   1/1       Running   1          4h        10.233.196.7    10.6.0.140
nginx-dm-2214564181-z8whk   1/1       Running   0          4h        10.233.182.65   10.6.0.187


[root@k8s-master-2 ~]# etcdctl --endpoints=https://10.6.0.140:2379 
>         --cert-file=/etc/kubernetes/ssl/etcd.pem 
>         --ca-file=/etc/kubernetes/ssl/ca.pem 
>         --key-file=/etc/kubernetes/ssl/etcd-key.pem 
>         member list
2017-08-08 02:39:52.830072 I | warning: ignoring ServerName for user-provided CA for backwards compatibility is deprecated
29262d49176888f5: name=etcd3 peerURLs=https://10.6.0.188:2380 clientURLs=https://10.6.0.188:2379 isLeader=true
d4ba1a2871bfa2b0: name=etcd1 peerURLs=https://10.6.0.140:2380 clientURLs=https://10.6.0.140:2379 isLeader=false
eca58ebdf44f63b6: name=etcd2 peerURLs=https://10.6.0.187:2380 clientURLs=https://10.6.0.187:2379 isLeader=false