使用Docker来部署NodeJs应用

Docker的环境无关性以及自动化特性实在是令人称赞,最近遇到的一个问题是,NodeJs使用8.x版本开发,但是线上服务器是7.x的,这时候又不能动线上的应用。

于是想到了使用Docker来部署NodeJs,服务器是Ubuntu的。

安装Docker

apt install docker.io

Dockerfile编写

由于默认的dockhub速度非常慢导致下载镜像慢,而且镜像下来的Ubuntu apt源又是国外的,简直是慢上加慢,本文使用daocloud.io的Ubuntu镜像以及阿里云的Ubuntu源

FROM daocloud.io/library/ubuntu
MAINTAINER xialeistudio<[email protected]>
ENV PATH $PATH:/opt/node/bin
ENV PORT 80
ENV HOST 0.0.0.0
# prepare
ADD sources.list /etc/apt/sources.list
RUN apt update
RUN apt install wget gcc python git -y
# nodejs
RUN wget https://npm.taobao.org/mirrors/node/latest-v8.x/node-v8.6.0-linux-x64.tar.gz
RUN tar xf node-v8.6.0-linux-x64.tar.gz
RUN mv node-v8.6.0-linux-x64 /opt/node
# app
RUN mkdir app
ADD . /root/app
WORKDIR /root/app
RUN /opt/node/bin/npm install --registry=https://registry.npm.taobao.org
# start app
ENTRYPOINT ["npm","start"]

指令解释一下

  1. 指定模板镜像

  2. 维护者信息,这是本人写的,所以署名为本人

  3. 环境变量定义

  4. 复制宿主机当前目录的sources.list到docker中的/etc/apt目录用来替换默认的Ubuntu源

  5. 更新apt并安装必要软件

  6. 从淘宝镜像站下载nodejs二进制版本

  7. 解压并移动到/opt/node目录

  8. 创建应用目录,并把宿主机当前文件夹下的所有文件拷贝到docker景象中

  9. 使用淘宝镜像安装npm包

  10. 启动APP

build镜像

docker build -t demo .

运行完毕后就可以使用docker images查看镜像了

启动容器

docker run -d -p 127.0.0.1:7001:80 demo

这时候容器已经启动,并通过端口转发监听在宿主机的7001端口上,配合nginx做反向代理就可以部署一个公网应用了。

不管你容器中部署何种版本的NodeJs都不会对宿主机造成影响,这点很重要。

基于iptables下OpenVPN访问权限控制

最近有博友咨询关于OpenVPN的用户访问权限控制的问题,即当用户连接进来以后,怎么去控制他的权限,我这里采用了一个脚本的方式自动添加,其它就是采用iptables的三层功能做路由与端口的访问控制,这里将这个shell分享出来,希望对有需要的朋友可以提供帮忙。

权限控制:

read -p "请选择您要做的操作:" caozuo
case $caozuo in
1) read -p "请输入您需要添加ERP访问权限的用户:" vpnuser
   while [ ! `more /etc/openvpn/staticip.txt | grep -w $vpnuser` ]
   doread -p "您需要添加权限的用户$vpnuser还未进行过首次登录,请让其登录一次再进行设置,请重新输入要添加权限的用户,退出请按Ctrl+C:" vpnuser
   done
     vpnuserip=`more /etc/openvpn/staticip.txt | grep $vpnuser | awk -F ‘,‘ ‘{print $2}‘`
        vpnuseripold=`more /etc/sysconfig/iptables | grep "$vpnuserip/32 -d 192.168.1.111/32 -p tcp -m tcp --dport 23 -j ACCEPT" | wc -l`if [ "$vpnuseripold" -ge "1" ]; thenread -p "您要添加权限的用户$vpnuser已经具有访问ERP的权限,不需要重复添加,按回车键退出"exitfi
     service iptables restart > nul
     iptables -I FORWARD 2 -p tcp -s $vpnuserip -d 192.168.1.111 --dport 23 -j ACCEPT
     echo " 您已经成功添加用户$vpnuser具有ERP访问权限"
     service iptables save > nul
;;

权限查看:

4) read -p "请输入要查询权限的用户名:" vpnuser
   while [ ! `more /etc/openvpn/staticip.txt | grep -w $vpnuser` ]
   doread -p "您输入用户名不存在,请重新输入用户名,退出请按Ctrl+C:" vpnuser
   done
   vpnuserip=`more /etc/openvpn/staticip.txt | grep $vpnuser | awk -F ‘,‘ ‘{print $2}‘`
   echo "您查询的用户$vpnuser具有以下访问权限:"
   more /etc/sysconfig/iptables | grep $vpnuserip | awk -F "" ‘{print $6,$12}‘ | sed -e ‘s/32端口号:g‘
;;

操作界面:

未分类

操作演示:

未分类

KVM NAT 模式下 虚拟机上不了外网

自己在服务器上搭建了KVM,准备使用kvm模式下的NAT模式给虚拟机上网,但是虚拟机ping外网ping不通。

我创建的虚拟是dhcp模式

未分类

这时候需要在宿主机开启路由转发的功能。

我的宿主机是centos7

编辑/etc/stsctl.conf 文件,增加 net.ipv4.ip_forward = 1

sysctl -p

这时候虚拟机ping外网就能通了。

系统部署之keepalived安装

安装文件

  • keepalived-1.3.5.tar.gz

编译安装

./configure
make && make install

修改配置

机器1

/etc/keepalived/keepalived.conf 

! Configuration File for keepalived
global_defs {
    notification_email {
        root@localhost
    }
    notification_email_from [email protected]
    smtp_server mail.example.com
    smtp_connect_timeout 30
    router_id LVS_DEVEL
}
vrrp_script chk_nginx {
    script "/etc/keepalived/check_nginx.sh" 
    interval 2 
    weight -5 
    fall 3  
    rise 2 
}
vrrp_instance VI_1 {
    state MASTER
    interface 网卡接口名
    virtual_router_id 51
    priority 101
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        虚拟地址1
    }
    track_script {
       chk_nginx 
    }
}
vrrp_instance VI_2 {
    state BACKUP
    interface 网卡接口名
    virtual_router_id 52
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        虚拟地址2
    }
    track_script {
       chk_nginx
    }
}

机器2

! Configuration File for keepalived
global_defs {
    notification_email {
        root@localhost
    }
    notification_email_from [email protected]
    smtp_server mail.example.com
    smtp_connect_timeout 30
    router_id LVS_DEVEL
}
vrrp_script chk_nginx {
    script "/etc/keepalived/check_nginx.sh" 
    interval 2 
    weight -5 
    fall 3  
    rise 2 
}
vrrp_instance VI_1 {
    state BACKUP
    interface 网卡接口名
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        虚拟地址1
    }
    track_script {
       chk_nginx 
    }
}
vrrp_instance VI_2 {
    state MASTER
    interface 网卡接口名
    virtual_router_id 52
    priority 101
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        虚拟地址2
    }
    track_script {
       chk_nginx
    }
}

nginx检查脚本

check_nginx.sh 

#!/bin/bash
counter=$(ps -C nginx --no-heading|wc -l)
if [ "${counter}" = "0" ]; then
    /usr/local/nginx/sbin/nginx
    sleep 2
    counter=$(ps -C nginx --no-heading|wc -l)
    if [ "${counter}" = "0" ]; then
        service keepalived stop
    fi
fi

系统服务

/lib/systemd/system/keepalived.service

[Unit]
Description=LVS and VRRP High Availability Monitor
After=syslog.target network.target

[Service]
Type=simple
PIDFile=/usr/local/var/run/keepalived.pid
KillMode=process
EnvironmentFile=-/usr/local/etc/sysconfig/keepalived
ExecStart=/usr/local/sbin/keepalived --dont-fork -D
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

测试

尝试关闭一台keepalived服务,查看另一台网络端口情况

注意事项

  • 防火墙启用状态下执行
firewall-cmd --direct --permanent --add-rule ipv4 filter INPUT 0 
  --in-interface enp0s8 --destination 224.0.0.18 --protocol vrrp -j ACCEPT

firewall-cmd --direct --permanent --add-rule ipv4 filter OUTPUT 0 
  --out-interface enp0s8 --destination 224.0.0.18 --protocol vrrp -j ACCEPT

firewall-cmd --reload

LVS+Keepalived实现前端高可用实现

一、Keepalived简介及VRRP原理

Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案,可以利用其来避免单点故障。一个LVS服务会有2台服务器运行Keepalived,一台为主服务器(MASTER),一台为备份服务器(BACKUP),但是对外表现为一个虚拟IP,主服务器会发送特定的消息给备份服务器,当备份服务器收不到这个消息的时候,即主服务器宕机的时候, 备份服务器就会接管虚拟IP,继续提供服务,从而保证了高可用性。Keepalived是VRRP的完美实现,因此在介绍keepalived之前,先介绍一下VRRP的原理。
VRRP原理
在一个VRRP虚拟路由器中,有多台物理的VRRP路由器,但是这多台的物理的机器并不能同时工作,而是由一台称为MASTER的负责路由工作,其它的都是BACKUP,MASTER并非一成不变,VRRP让每个VRRP路由器参与竞选,最终获胜的就是MASTER。MASTER拥有一些特权,比如,拥有虚拟路由器的IP地址,我们的主机就是用这个IP地址作为静态路由的。拥有特权的MASTER要负责转发发送给网关地址的包和响应ARP请求。
VRRP通过竞选协议来实现虚拟路由器的功能,所有的协议报文都是通过IP多播(multicast)包(多播地址224.0.0.18)形式发送的。虚拟路由器由VRID(范围0-255)和一组IP地址组成,对外表现为一个周知的MAC地址。所以,在一个虚拟路由 器中,不管谁是MASTER,对外都是相同的MAC和IP(称之为VIP)。客户端主机并不需要因为MASTER的改变而修改自己的路由配置,对客户端来说,这种主从的切换是透明的。
在一个虚拟路由器中,只有作为MASTER的VRRP路由器会一直发送VRRP通告信息(VRRPAdvertisement message),BACKUP不会抢占MASTER,除非它的优先级(priority)更高。当MASTER不可用时(BACKUP收不到通告信息), 多台BACKUP中优先级最高的这台会被抢占为MASTER。这种抢占是非常快速的(<1s),以保证服务的连续性。由于安全性考虑,VRRP包使用了加密协议进行加密。

二、LVS+Keepalived实现前端高可用实现

1、 实验环境

[root@localhost ~]# uname -r
2.6.32-696.el6.x86_64
[root@localhost ~]# rpm -q keepalived
keepalived-1.2.13-5.el6_6.x86_64

时间同步:

[root@node2 ~]# ntpdate 192.168.1.200

各主机添加host能相互解析

关闭iptables及selinux

未分类

2、配置Keepalived

1)、在192.168.1.200及192.168.1.201上安装Keepalived(yum install keepalived -y)

2)、配置Keepalived

192.168.1.200配置文档:

global_defs {               //全局配置段
   notification_email {        //管理员通知邮箱,可不填写

   }
   notification_email_from root
   smtp_server 127.0.0.1       //邮件服务器地址
   smtp_connect_timeout 30
   router_id LVS_10         //主调度路由器名称,需和备份服务器保持一致
}

vrrp_instance VI_1 {         //VRRPD配置段
    state MASTER         //设置MASTER或BACKUP         
    interface eth0         //设置VIP物理地址的接口
    virtual_router_id 51            //虚拟路由ID号,每组需保持一致
    priority 100                    //优先级,越大越有限
    advert_int 1                    //心跳频率(秒)
    authentication {        
        auth_type PASS          //组播认证方式
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.1.250/24 dev eth0 label eth0:1 //VIP配置,可以有多个VIP
    }
}

virtual_server 192.168.1.250 80 {        //LVS段配置,注意端口
    delay_loop 3                       //健康检测时间
    lb_algo wrr                        //算法rr,wrr,lc,wlc等
    lb_kind DR                        //集群工作模式(nat、dr、tunl、fullnat)
    persistence_timeout 0            //会话保持时间
    protocol TCP                     //协议

    real_server 192.168.1.202 80 {    //realserver配置
        weight 1                    //权重
        TCP_CHECK {                 //健康检查(多种方式)
            connect_port 80     //检测端口
            connect_timeout 3   //超时时间
            nb_get_retry 3      //重试次数
            delay_before_retry 3 //重试间隔
        }
    }

    real_server 192.168.1.203 80 {
        weight 1
        TCP_CHECK {
            connect_port 80
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
        }
    }
}

192.168.1.201配置与上面类似更改state及priority

3、配置realserver

192.168.1.202配置:

DR realserver脚本:

#!/bin/bash
#
#script to start LVS DR real server.   
# description: LVS DR real server   
#   
.  /etc/rc.d/init.d/functions
VIP=192.168.1.250 #修改你的VIP  
host=`/bin/hostname`
case "$1" in
start)
       # Start LVS-DR real server on this machine.   
        /sbin/ifconfig lo down
        /sbin/ifconfig lo up
        echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
        echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
        echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore   
        echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
        /sbin/ifconfig lo:0 $VIP broadcast $VIP netmask 255.255.255.255 up  
        /sbin/route add -host $VIP dev lo:0
;;  
stop)
        # Stop LVS-DR real server loopback device(s).  
        /sbin/ifconfig lo:0 down   
        echo 0 > /proc/sys/net/ipv4/conf/lo/arp_ignore   
        echo 0 > /proc/sys/net/ipv4/conf/lo/arp_announce   
        echo 0 > /proc/sys/net/ipv4/conf/all/arp_ignore   
        echo 0 > /proc/sys/net/ipv4/conf/all/arp_announce
;;  
status)
        # Status of LVS-DR real server.  
        islothere=`/sbin/ifconfig lo:0 | grep $VIP`   
        isrothere=`netstat -rn | grep "lo:0" | grep $VIP`   
        if [ ! "$islothere" -o ! "isrothere" ];then
            # Either the route or the lo:0 device   
            # not found.   
            echo "LVS-DR real server Stopped."   
        else
            echo "LVS-DR real server Running."   
        fi
;;
*)
            # Invalid entry.   
            echo "$0: Usage: $0 {start|status|stop}"   
            exit 1
;;
esac

执行脚本:

[root@node2 ~]# chmod +x realserver.sh 
[root@node2 ~]# ./realserver.sh start

检查相关脚本配置是否正确

ifconfig及cat

在node2上安装httpd后添加测试网页

echo "<h1>node2.psemily.com</h1>" > /var/www/html/index.html

node3类似操作

4、验证

启动192.168.1.200及192.168.1.201上keepalived后访问VIP:192.168.1.250

下图:图一:调度情况,图二为正常访问,图三为192.168.1.202停止httpd后的访问,图四为停止192.168.1.200后,192.168.1.201的日志

未分类

未分类

未分类

未分类

5、Keepalived的健康检测

HTTP_GET|SSL_GET|TCP_CHECK|SMTP_CHECK|MISC_CHECK

1)HTTP_GET|SSL_GET

这里有几个要点:

a、两者都有两种检测方式,一种是简单的基于返回码确认;另一种是基于确认后端页面内容hash值,确认前后是否发生变化(是不是感觉有点高端,还有简单的防止页面被篡改的作用,当然,动态页面显然不行);

b、两者都是处理简单的GET请求,基于post返回值确认是否正常,这种方法显然不适用 ,不过POST方式是可以通过MISC_CHECK方式进行支持检测的;

c、两者配置语法上相同,只不过类型名不同而已 。同属于大的web请求范畴,只不过一个走的HTTP协议,一个走的HTTPS协议;

基于状态码检测,配置如下:

real_server 192.168.1.250 80 {
      weight 1
      HTTP_GET {
          url {
          path /index.html
          status_code 200      #http://192.168.1.250/index.html的返回状态码
            }
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
        }

基于后端页面内容检测,配置如下:

real_server 192.168.1.250 80 {
     weight 1
     HTTP_GET {
       url {
       path /index.html
       digest 1366dcc22ca042f5e6a91232bc8f4c9f #http://192.168.1.202/index.html的digest值
        }
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
        }

digest是由genhash(通过该命令可以获取页面的hash串)生成,语法如下:

[root@localhost keepalived]# genhash -s 192.168.1.202 -p 80 -u /index.html
MD5SUM = 1366dcc22ca042f5e6a91232bc8f4c9f

2)TCP_CHECK

基于TCP的检测,配置如下:

real_server 192.168.1.250 80 {
        weight 100
        TCP_CHECK {
            connect_timeout 3  
            nb_get_retry 3  
            delay_before_retry 3  
            connect_port 80    //检测端口
        }
    }

3)MISC_CHECK

调用外部配置文件进行检测,配置如下:

MISC_CHECK {
    misc_path <STRING>|<QUOTED-STRING># 外部程序或者脚本路径
    misc_timeout <INT># 执行脚本的超时时间
    misc_dynamic #如果设置了misc_dynamic,healthchecker程序的退出状态码会用来动态调整服务器的权重(weight).
    #返回0:健康检查OK,权重不被修改
    #返回1:健康检查失败,权重设为0
    #返回2-255:健康检查OK,权重设置为:退出状态码-2,比如返回255,那么weight=255-2=253
}

脚本是可以选择传参数还是不传参数的,示例如下:

#不传参配置
real_server 192.168.1.250 80 {
    weight 1
    MISC_CHECK {
      misc_path /usr/local/bin/script.sh
    }
}
#传参配置
real_server 192.168.1.250 80 {
    weight 1
    MISC_CHECK {
      misc_path "/usr/local/bin/script.sh  arg1  arg2"
    }
}

6、实验中遇到的问题

实验中用当所有配置都配置完成后,有浏览器访问VIP时,出现了只调度到一个realserver上的情景,过一段时间后能调度到另外一个realserver上,停止其中一个httpd,浏览器访问时出现不能访问,过段时间才能访问运行的另一个realserver上。

查找网上资料后有以下两种方案:

1、修改persistence_timeout 0 将连接保持时间设置为0,修改后用浏览器访问还是没能解决。用curl可以看出效果

2、ipvsadm的时间

[root@localhost keepalived]# ipvsadm -l --timeout
Timeout (tcp tcpfin udp): 900 120 300

修改时间后访问仍然不行

[root@localhost keepalived]# ipvsadm --set 1 1 1

上述两种方法均不能解决浏览器访问调度到一个realserver上的问题,但是用curl能看出效果,在此先做个记录。

Kubernetes 1.6到1.7升级记录

最近正在制定将团队生产环境的Kubernetes集群从1.6升级1.7的计划。 Kubernetes 1.8已经发布,所以准备考虑从1.6到1.7的升级。

准备

当前1.7的最新版本是1.7.8。在做准备之前需要仔细读一遍官方的Kubernetes 1.7 (https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.7.md#action-required-before-upgrading)。

使用ansible升级k8s的核心组件

目前我们总共有两个高可用的Kubernetes集群,分别是测试环境和生产环境,版本都是1.6.10。 这两套环境的Kubernetes集群都是基于ansible自动部署,在1.6.x的每个小版本的升级也都是使用ansible完成。

这里准备的升级步骤是本地VM环境 => 测试环境 => 生产环境。 先使用ansible在本地VM中部署k8s 1.6.10的集群,然后再使用ansible将本地1.6升级到1.7并做一些验证,再升级测试环境,测试环境稳定运行一段时间后完成生产环境的升级。

本地VM环境中的ansible play的十分顺利,集群的各个核心组件已经成功的升级到1.7.8。

以容器形式运行组件的升级

接下来要对一些以容器形式运行的组件升级,对应版本如下:

  • flannel 0.9.0
  • kube-dns 1.14.5
  • dashboard 1.7.1

升级kube-dns的注意事项

kube-dns 1.14.5部署文件的地址在gitlab中发生了变化,在内容上使用Deployment替换了Replication Controller。

wget https://raw.githubusercontent.com/kubernetes/kubernetes/e1d6bcc22736a15ce662b3bd1009a16cdde5cd86/cluster/addons/dns/kube-dns.yaml.base
wget https://raw.githubusercontent.com/kubernetes/kubernetes/e1d6bcc22736a15ce662b3bd1009a16cdde5cd86/cluster/addons/dns/transforms2sed.sed

查看transforms2sed.sed:

s/__PILLAR__DNS__SERVER__/$DNS_SERVER_IP/g
s/__PILLAR__DNS__DOMAIN__/$DNS_DOMAIN/g
s/__MACHINE_GENERATED_WARNING__/Warning: This is a file generated from the base underscore template file: __SOURCE_FILENAME__/g

将$DNS_SERVER_IP替换成10.96.0.10,将DNS_DOMAIN替换成cluster.local。 注意$DNS_SERVER_IP要和kubelet设置的–cluster-dns参数一致

执行:

cd ~/k8s/kube-dns
sed -f transforms2sed.sed kube-dns.yaml.base > kube-dns.yaml
  • 上面的变量DNS_SERVER要和kubelet设置的–cluster-dns参数一致。
kubectl create -f kube-dns.yaml

查看kube-dns的Pod,确认所有Pod都处于Running状态:

kubectl get pods --all-namespaces
NAMESPACE     NAME                                    READY     STATUS    RESTARTS   AGE
kube-system   kube-dns-3468831164-chjw5               3/3       Running   0          3m

测试一下DNS功能是否好用:

kubectl run curl --image=radial/busyboxplus:curl -i --tty

nslookup kubernetes.default
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local

kube-dns是Kubernetes实现服务发现的重要组件之一,默认情况下只会创建一个DNS Pod,在生产环境中我们可能需要对kube-dns进行扩容。 有两种方式:

  • 手动扩容 kubectl –namespace=kube-system scale deployment kube-dns –replicas=

  • 使用DNS Horizontal Autoscaler

升级dashboard的注意事项

因为之前部署Dashboard 1.6时创建了ServiceAccount kubernetes-dashboard,并分配了cluster-admin的权限。 所以在升级前应该先删除掉这个ServiceAccount:

kubectl delete  serviceaccount kubernetes-dashboard -n kube-system
kubectl delete  clusterrolebinding kubernetes-dashboard

1.7.x版本的dashboard对安全做了增强,默认需要以https的方式访问,增加了登录的页面,同时增加了一个gcr.io/google_containers/kubernetes-dashboard-init-amd64的init容器。

另外需要注意dashboard调整了部署文件的源码目录结构:

mkdir -p ~/k8s/
wget https://raw.githubusercontent.com/kubernetes/dashboard/b44f7cc5fde4dbe2a884b1e32a2b363d8031e4ca/src/deploy/recommended/kubernetes-dashboard.yaml
kubectl create -f kubernetes-dashboard.yaml

kubernetes-dashboard.yaml文件中的ServiceAccount kubernetes-dashboard只有相对较小的权限,因此我们创建一个kubernetes-dashboard-admin的ServiceAccount并授予集群admin的权限,创建kubernetes-dashboard-admin.rbac.yaml:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-admin
  namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard-admin
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard-admin
  namespace: kube-system
kubectl create -f kubernetes-dashboard-admin.rbac.yaml
serviceaccount "kubernetes-dashboard-admin" created
clusterrolebinding "kubernetes-dashboard-admin" created

查看kubernete-dashboard-admin的token:

kubectl -n kube-system get secret | grep kubernetes-dashboard-admin
kubernetes-dashboard-admin-token-mrngz   kubernetes.io/service-account-token   3         18m

kubectl describe -n kube-system secret kubernetes-dashboard-admin-token-mrngz
Name:           kubernetes-dashboard-admin-token-mrngz
Namespace:      kube-system
Labels:         <none>
Annotations:    kubernetes.io/service-account.name=kubernetes-dashboard-admin
                kubernetes.io/service-account.uid=906d3aa6-b06d-11e7-afdd-080027d9d784

Type:   kubernetes.io/service-account-token

Data
====
ca.crt:         1302 bytes
namespace:      11 bytes
token:          ......

在dashboard的登录窗口使用上面的token登录。

使用Docker和Kubernetes构建可伸缩的微服务

从现在开始,我们将从更高的维度讨论微服务,涵盖了组织敏捷性、设计和依赖的思考、领域驱动设计以及Promise理论。当我们深入使用之前介绍的三个流行的微服务框架:Spring Boot、Dropwizard和WildFly Swarm,我们能够使用它们开箱即用的能力去构建一个暴露或者消费REST服务的应用,能够使用外部环境对应用进行配置,可以打包成一个可执行的jar,同时提供Metrics信息,但这些都是围绕着一个微服务实例。当我们需要管理微服务之间的依赖、集群的启动和关闭、健康检查以及负载均衡的时候,我们使用微服务架构会面临什么问题呢?本章,我们将讨论这些高阶话题用来理解部署微服务时面对的挑战。

当我们开始将我们的应用和服务按照微服务的思路进行拆分后,我们将面临这样的场景:我们有了更多的服务、更多的二进制内容、更多的配置,更多的交互点等等。传统方式是将这些构建成一个二进制单元,比如:WARs或者EARs,然后将其打包后等待运维人员将它部署到我们指定的应用服务器上。如果对于高可用有要求,也会将应用服务器进行分布式部署,形成集群,依靠负载均衡、共享磁盘(数据库)等方式提升可用性。传统运维体系下也开发了一些自动化部署的工具,比如:Chef和Ansible,工具虽然简化了部署,但是开发人员还是需要面对部署时容易出现的问题,比如:配置、环境等不可预知的问题。
  • chef
    Chef是由Ruby与Erlang写成的配置管理软件,它以一种纯Ruby的领域专用语言(DSL)保存系统配置“烹饪法(recipes)”或“食谱(cookbooks)”。Chef由Opscode公司开发,并在Apache协议版本2.0下开源发布。

  • ansible
    使用python构建,中文化资料比较多,Ansible的简洁界面和可用性非常迎合系统管理员的想法。

在传统方式下尝试微服务的部署,将会是个糟糕的结果。如何解决应用服务器在开发、测试以及生产环境的不同配置?如果没有,如何能够捕获到这些配置的变更?而这些变更如何确认已经运行在应用服务器中了?运行时软件环境,比如:操作系统、JVM以及相关的组件在生产和开发环境下的不同问题如何解决?如果我们的应用已经针对特定的JVM做了调优,这些调优参数会不会影响到他人?如果部署微服务,你会选择使用进程隔离的方式将它们部署在一台机器上吗?如果其中一个微服务实例消耗了系统100%的资源,该如何是好?如果过度的占用了I/O或者共享存储怎么办?如果部署了多个微服务实例的宿主机崩溃了怎么办?我们的应用为此做过应对方案吗?将应用分拆为微服务是小,但面对的问题显然会更多。

不可变的递交

不可变的递交(Immutable delivery)原则可以帮助我们应对上述的部分问题,在这个体系下,我们将使用镜像技术来尝试减少开发到生产的步骤。例如:构建系统能够输出一个包含了操作系统、JVM、配置、相关组件的镜像,我们可以将它部署到一个环境中,测试它,如果通过测试,最终可以将它部署到生产环境中而不用担心开发流程使交付的软件缺少了什么。如果你想变更应用,那么可以回到刚才这个流程的最开始,应用你的修改,重新构建镜像,最终完成部署,如果出乎你的意料,程序有问题,你可以直接选择回滚到上一个正确的镜像而不用担心遗漏了什么。

这听起来很好,但是我们怎么做到呢?将应用打包成一个jar还不足以足够做到这些。JVM是底层实现,我们如何将它也打包进去,而JVM又使用了操作系统级别组件,这些内容我们都要打包,除此之外,我们还需要配置、环境变量、权限等等,这些都需要打包,而这些内容无法被打包到一个可执行jar中去。更加重要的是,不止java一种微服务,如果程序使用NodeJS、Golang编写,我们还要针对不同的语言环境做不同的打包。你可能想使用自动化手段完成这些软件的安装,将基础设施作为服务(IaaS),使用它们的API完成环境的搭建。事实上Netflix已经使用了自动化构建工具来完成VM的构建,并利用这项技术实现了不可变的递交,但是VM很难管理、更新和变更,而每个VM都有自己完备的虚拟化环境,对资源有些浪费。

那么有什么更加轻量化的打包和镜像化方式让我们使用吗?

Docker,Docker,Docker

Docker是近几年出现用于解决不可变递交的优雅解决方案,它允许我们将应用以及应用的所有依赖(包括了:OS,JVM以及其他组件)打包成为一个轻量的、分层的镜像格式。然后Docker使用这些镜像,运行它们,产生实例,而这些实例都运行在Linux containers中,在Linux containers中,会带来CPU、内存、网络以及磁盘的隔离。在这种模式下,这些容器实例就是一种应用虚拟化的方式,它运行一个进程去执行,你甚至可以在实例中运行ps查看你的进程,而且这个容器实例具备访问CPU、内存、磁盘和网络的能力,但是它只能使用指定好的配额。例如:能够启动一个Docker容器,只为它分配一部分的CPU、内存以及I/O的访问限制。如果在Linux containers外部去看,在主机上,这个容器就是一个进程,不需要设备驱动的虚拟化、操作系统、网络栈以及特殊的中间层,它仅仅是一个进程。这意味着,我们可以在一台机器上部署尽可能多的容器,提供了比虚拟机更高的部署密度。

在这些激动人心的特性下,其实没有革命性的技术。Docker使用到的技术有:cgroups、namespaces以及chroot,这些都已经在Linux内核中运行了相当长的时间,而这些技术被Docker用来构造应用虚拟化技术。Linux containers已经推出了十几年,而进程虚拟化技术在Solaris和FreeBSD上出现的时间更早。以往使用这些技术的lxc会比较复杂,而Docker通过简单的API以及优秀的用户体验使得Linux containers的运用变得火热起来,Docker通过一个客户端命令工具能够与Linux containers进行交互,去部署Docker镜像,而Docker镜像的出现改变了我们打包和交付软件的方式。

一旦你拥有了镜像,可以迅速的转化为Linux containers,镜像是按照层进行构建的,一般会在一个基础的层(例如:RHEL、 Debian等)上进行构建,然后包含应用所需的内容,构建应用其实也就是在基础层上进行一层一层的镜像构建。镜像的出现,是的发布到各种环境变得容易,不会在面对一堆零散的内容,如果发现基础镜像中有问题,可以进行重新构建,其他镜像进行重新选择构建即可,这使得从开发环境到测试,再到生产环境减少了人工干预发布内容的环节,如果我们要新发布一版本,只需要重新构建一个镜像即可,而改动只是去修改了镜像中对应的层。

构建了镜像,但是我们怎样启动一个应用?怎样停止它?怎样做健康检查?怎样收集应用的日志、Metrics等信息,使用标准的API可以使我们自己构建工具来完成这些工作。出色的集群机制,例如服务发现、负载均衡、失败容错以及配置使得开发人员很容易获得这些特性。

Docker相关的技术可以关注 https://www.gitbook.com/book/weipeng2k/the-docker-book/details

Kubernetes

外界都知晓Google使用Linux containers技术来支撑其扩展性,事实上Google的所有应用都运行在Linux containers上,并且被他们的管理系统Brog进行着管理。前Google工程师Joe Beda说,公司每周要启动超过20亿次的容器,Google甚至投入资源涉及到linux底层技术来支持其容器在生产环境的运用。在2006年,Google开始了一个名叫 进程容器 的项目,最终演变成为了cgroups,而它在2008被合并到了Linux核心,同年正式发布。Google在拥有极强的运维容器的背景下,其对构建容器平台的影响力就不言而喻了,事实上,一些流行的容器管理项目都受到了Google的影响。
  • Cloud Foundry
    它的创立者Derek Collison和Vadim Spivak都在Google工作过,并且使用Borg系统很多年

  • Apache Mesos
    它的创立者Ben Hindman在Google实习过,与Google的诸多工程师有过容器技术的交流(围绕容器集群、调度和管理等技术)

  • Kubernetes
    开源的容器集群管理平台和社区,创建它的工程师,同时也在Google创建了Borg

    在Docker震惊技术届的2013年,Google决定是时候开源他们下一代的技术–Borg,而它被命名为Kubernetes。今天,Kubernetes是一个巨大、开放和快速成长的社区,来自Google、Red Hat、CoreOS以及其他的个体在为它做出贡献。Kubernetes为在可伸缩的Linux containers下运行微服务提供了非常多有价值的功能,Google将近20年的运维经验都浓缩到了Kubernetes,这对我们使用微服务部署产生了巨大的影响。大部分高流量的互联网企业在这个领域耕耘了很长时间(Netflix、Amazon等)尝试构建的伸缩技术,在Kubernetes中都已经默认进行了集成,在正式深入例子之前,我们先介绍一些Kubernetes的概念,接下来在后面的章节将会用它来挂历一个微服务集群。

Pods

一个Pod是一个或者多个Docker容器的组合,一般情况下一个Pod对应一个Docker容器,应用部署在其中。

Kubernetes进行编排、调度以及管理Pod,当我们谈到一个运行在Kubernetes中的应用时,指的是运行在Pod中的Docker容器。一个Pod有自己的IP地址,所有运行在这个Pod中的容器共享这个IP(这个不同于普通的Docker容器,普通的Docker容器每个实例都有一个IP),当一个卷挂载到Pod,这个卷也能够被Pod中的容器共同访问。

关于Pod需要注意的一点是:它们是短暂的,这代表着它们会在任何时候消失(不是因为服务崩溃就是集群cluster杀死了它),它们不像VM一样引起你的额外注意。Pods能够在任意时刻被销毁,而这种意外的失败就如同介绍微服务架构中任何事情都会失败一样(design for failure),我们强烈建议在编写微服务时时刻记着这个建议。和之前介绍的其他原则相比,这个建议显得更加重要。

Kubernetes的最小部署单元是Pod而不是容器。作为First class API公民,Pods能被创建,调度和管理。简单地来说,像一个豌豆荚中的豌豆一样,一个Pod中的应用容器同享同一个上下文(比如:PID名字空间、网络等)。在实际使用时,我们一般不直接创建Pods, 我们通过replication controller来负责Pods的创建,复制,监控和销毁。一个Pod可以包括多个容器,他们直接往往相互协作完成一个应用功能。

标签(Label)

标签(Label)是一个能分配给Pods的简单键值对,比如:release=stable或者tier=backend,Pods(或者其他资源,但是我们当前只关注Pods)可以拥有多个标签并且可以以松耦合的方式进行分组,这在Kubernetes的使用过程中非常常见。因此一点也不奇怪,Google使用这种简单的方式用来区分不同的容器,并以此来构建大规模伸缩的集群。当我们用标签区分了Pods之后,我们可以使用 label selector 来按照分组来查询所有的Pods,例如:如果我们有一些Pods打上了tier=backend的标签,而其他的一些打上了tier=frontend标签,只需要使用 label selector 表达式 tier != frontend就可以完成对所有没有打上tier=frontend的Pods进行查询,而 label selector 在接下来介绍的 replication controllers 和 services 所使用。

复制控制器(Replication Controllers)

当我们讨论微服务的可伸缩性时,可能想的是将给定的一组微服务部署到多个实例(机器)上,用多个实例的部署来增加伸缩性。Kubernetes为伸缩性定义了一个叫做 Replication Controllers 的概念,它能够管理给定的一组微服务的多个复制体(replicas),例如:我们需要管理许多打上 tier=backend and release=stable 的需要Pods,可以创建一个复制控制器,该控制器拥有对应的 label selector ,此时它就能够在集群中以replicas的形式控制和管理这些Pods。如果我们设置replica的数量为10,当Kubernetes会确定当前的复制控制器是否达到了该状态,如果此刻只有5个,那么Kubernetes就会循环创建剩余的5个,当有20个运行着,Kubernetes将会选择停止10个。Kubernetes将会尽可能的保持设定的10个replica的状态,你可以认为使用复制控制器来控制集群的数量是非常容易的事情,在接下来的章节中,我们会看到使用复制控制器的例子。

服务(Services)

我们最后需要理解的Kubernetes概念是服务(Service), Replication Controllers 能控制一个服务下的多个复制体(replicas),我们也观察到Pods能够被停止(要么自己crash、或者被kill,也有可能被复制控制器停止),因此,当我们尝试与一组Pods进行通信时,不应该依赖于具体的IP(每个Pod都有自己的IP),我们需要的是能够以组的形式访问这些Pods的方式,以组的形式发现它们,可能的话能够以负载均衡的方式访问它们,这个就是 服务(Service) 需要做的。它(服务)允许我们通过一个 label selector 获取一组Pods,将它们抽象为一个虚拟IP,然后以这个虚拟IP来让我们对这些Pods进行发现和交互,我们将在接下来的章节中介绍具体的例子。

Service是定义一系列Pod以及访问这些Pod的策略的一层抽象。Service通过Label找到Pod组。因为Service是抽象的,所以在图表里通常看不到它们的存在

了解这些简单的概念,Pods、Labels、Replication Controllers和services,我们能够以可伸缩的模式,用Google的实践,来管理微服务。这些实践花费了多年,经历了多次失败总结出来的经验之谈,而这个模式能够解决复杂的问题,因此强烈建议学习这些概念以及实践,使用Kubernetes来管理你的微服务。

开始使用Kubernetes

Docker和Kubernetes都是基于Linux本地技术的产品,因此它们需要运行在一个基于Linux的环境中,我们假设大部分的Java开发人员都是工作在Windows或者Mac下,我们推荐在Linux环境下进行相关的实践。

接下来的内容,作者作为redhat的员工,开始介绍CDK(RedHat Container Development Kit),然后是CDK的安装,译者觉得CDK没有多大的参考性,因此将其替换成了对Kubernetes官方的MiniKube使用,并基于MiniKube在linux机器上搭建Kubernetes。

Kubernetes之MiniKube的安装

笔者准备了aliyun oss 下载,比googleapis快许多

该文档介绍如何运行起一个本地Kubernetes集群,需要一个支持Hyper-V虚拟化的CPU以及至少8GB CPU

笔者的环境是 ubuntu 16.04 / amd k8 4 core CPU / 16 gb mem

需要提前安装VirtualBox5.1,自行到官网上进行安装,不要图简单使用ubuntu默认的,那个平常自己使没问题,但是MiniKube不行

安装MiniKube

wget http://029145.oss-cn-hangzhou.aliyuncs.com/minikube-linux-amd64 mv minikube-linux-amd64 minikube chmod u+x minikube sudo mv minikube /usr/local/bin/ 

安装Kubectl

wget http://029145.oss-cn-hangzhou.aliyuncs.com/kubectl chmod u+x kubectl sudo mv kubectl /usr/local/bin/ 

启动MiniKube

通过以下命令启动minikube,该过程会下载一个ISO镜像,然后完成启动。

minikube start 

下载依赖的镜像

这个过程最为复杂,当启动minikube时,会自动下载一些镜像,但是这些镜像都被墙了,但是我们可以从aliyun的仓库下载对应的镜像,然后将其重命名。在启动完minikube后,使用minikube ssh可以登录到后台,然后运行下面的命令完成镜像的下载和别名设置。

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 gcr.io/google_containers/pause-amd64:3.0  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kube-addon-manager-amd64:v6.1 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kube-addon-manager-amd64:v6.1 gcr.io/google-containers/kube-addon-manager:v6.1  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kubedns-amd64:1.9 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kubedns-amd64:1.9 gcr.io/google_containers/kubedns-amd64:1.9  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kube-dnsmasq-amd64:1.4 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kube-dnsmasq-amd64:1.4 gcr.io/google_containers/kube-dnsmasq-amd64:1.4  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/exechealthz-amd64:1.2 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/exechealthz-amd64:1.2 gcr.io/google_containers/exechealthz-amd64:1.2  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kubernetes-dashboard-amd64:v1.5.0 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kubernetes-dashboard-amd64:v1.5.0 gcr.io/google_containers/kubernetes-dashboard-amd64:v1.5.1 

测试echoserver

运行命令创建一个echoserver服务,运行如下命令:

kubectl run hello-minikube --image=registry.cn-hangzhou.aliyuncs.com/google-containers/echoserver:1.4 --port=8080 kubectl expose deployment hello-minikube 

然后运行minikube service hello-minikube –url,将会返回hello-minikube的url,然后可以基于该url做一下测试。

$ minikube service hello-minikube --url http://192.168.99.100:31907 $ curl http://192.168.99.100:31907/123 CLIENT VALUES: client_address=172.17.0.1 command=GET real path=/123 query=nil request_version=1.1 request_uri=http://192.168.99.100:8080/123  SERVER VALUES: server_version=nginx: 1.10.0 - lua: 10001  HEADERS RECEIVED: accept=*/* host=192.168.99.100:31907 user-agent=curl/7.47.0 BODY: -no body in request- 

可以看到请求对应的url,有数据返回,当然也可以启动dashboard,比如运行minikube dashboard将会打开管理页面。

Kubernetes 1.8 集群搭建

目前 Kubernetes 1.8.0 已经发布,1.8.0增加了很多新特性,比如 kube-proxy 组建的 ipvs 模式等,同时 RBAC 授权也做了一些调整,国庆没事干,所以试了一下;以下记录了 Kubernetes 1.8.0 的搭建过程。

一、环境准备

目前测试为 5 台虚拟机,etcd、kubernetes 全部采用 rpm 安装,使用 systemd 来做管理,网络组件采用 calico,Master 实现了 HA;基本环境如下

未分类

本文尽量以实际操作为主,因为写过一篇 Kubernetes 1.7 搭建文档(https://mritd.me/2017/07/21/set-up-kubernetes-ha-cluster-by-binary/),所以以下细节部分不在详细阐述,不懂得可以参考上一篇文章;本文所有安装工具均已打包上传到了 百度云(https://pan.baidu.com/s/1nvwZCfv) 密码: 4zaz,可直接下载重复搭建过程,搭建前请自行 load 好 images 目录下的相关 docker 镜像

二、搭建 Etcd 集群

2.1 生成 Etcd 证书

同样证书工具仍使用的是 cfssl,百度云的压缩包里已经包含了,下面直接上配置(注意,所有证书生成只需要在任意一台主机上生成一遍即可,我这里在 Master 上操作的)

etcd-csr.json

{
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "O": "etcd",
      "OU": "etcd Security",
      "L": "Beijing",
      "ST": "Beijing",
      "C": "CN"
    }
  ],
  "CN": "etcd",
  "hosts": [
    "127.0.0.1",
    "localhost",
    "10.10.1.5",
    "10.10.1.6",
    "10.10.1.7",
    "10.10.1.8",
    "10.10.1.9"
  ]
}

etcd-gencert.json

{
  "signing": {
    "default": {
        "usages": [
          "signing",
          "key encipherment",
          "server auth",
          "client auth"
        ],
        "expiry": "87600h"
    }
  }
}

etcd-root-ca-csr.json

{
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "O": "etcd",
      "OU": "etcd Security",
      "L": "Beijing",
      "ST": "Beijing",
      "C": "CN"
    }
  ],
  "CN": "etcd-root-ca"
}

最后生成证书

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

证书生成后截图如下

未分类

2.2 搭建集群

首先分发证书及 rpm 包

# 分发 rpm
for IP in `seq 5 7`; do
    scp etcd-3.2.7-1.fc28.x86_64.rpm [email protected].$IP:~
    ssh [email protected].$IP rpm -ivh etcd-3.2.7-1.fc28.x86_64.rpm
done

# 分发证书
for IP in `seq 5 7`;do
    ssh [email protected].$IP mkdir /etc/etcd/ssl
    scp *.pem [email protected].$IP:/etc/etcd/ssl
    ssh [email protected].$IP chown -R etcd:etcd /etc/etcd/ssl
    ssh [email protected].$IP chmod -R 644 /etc/etcd/ssl/*
    ssh [email protected].$IP chmod 755 /etc/etcd/ssl
done

然后修改配置如下(其他两个节点类似,只需要改监听地址和 Etcd Name 即可)

docker1.node ➜  ~ cat /etc/etcd/etcd.conf

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

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

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

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

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

最后启动集群并测试如下

systemctl daemon-reload
systemctl start etcd
systemctl enable etcd

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

未分类

三、搭建 Master 节点

3.1 生成 Kubernetes 证书

生成证书配置文件需要借助 kubectl,所以先要安装一下 kubernetes-client 包

rpm -ivh kubernetes-client-1.8.0-1.el7.centos.x86_64.rpm

生成证书配置如下

admin-csr.json

{
  "CN": "admin",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "system:masters",
      "OU": "System"
    }
  ]
}

k8s-gencert.json

{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
        "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ],
        "expiry": "87600h"
      }
    }
  }
}

k8s-root-ca-csr.json

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

kube-proxy-csr.json

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

kubernetes-csr.json

{
    "CN": "kubernetes",
    "hosts": [
        "127.0.0.1",
        "10.254.0.1",
        "10.10.1.5",
        "10.10.1.6",
        "10.10.1.7",
        "10.10.1.8",
        "10.10.1.9",
        "localhost",
        "kubernetes",
        "kubernetes.default",
        "kubernetes.default.svc",
        "kubernetes.default.svc.cluster",
        "kubernetes.default.svc.cluster.local"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}

最后生成证书及配置文件

# 生成证书
cfssl gencert --initca=true k8s-root-ca-csr.json | cfssljson --bare k8s-root-ca

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

# 生成配置
export KUBE_APISERVER="https://127.0.0.1:6443"
export BOOTSTRAP_TOKEN=$(head -c 16 /dev/urandom | od -An -t x | tr -d ' ')
echo "Tokne: ${BOOTSTRAP_TOKEN}"

cat > token.csv <<EOF
${BOOTSTRAP_TOKEN},kubelet-bootstrap,10001,"system:kubelet-bootstrap"
EOF

echo "Create kubelet bootstrapping kubeconfig..."
kubectl config set-cluster kubernetes 
  --certificate-authority=k8s-root-ca.pem 
  --embed-certs=true 
  --server=${KUBE_APISERVER} 
  --kubeconfig=bootstrap.kubeconfig
kubectl config set-credentials kubelet-bootstrap 
  --token=${BOOTSTRAP_TOKEN} 
  --kubeconfig=bootstrap.kubeconfig
kubectl config set-context default 
  --cluster=kubernetes 
  --user=kubelet-bootstrap 
  --kubeconfig=bootstrap.kubeconfig
kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

echo "Create kube-proxy kubeconfig..."
kubectl config set-cluster kubernetes 
  --certificate-authority=k8s-root-ca.pem 
  --embed-certs=true 
  --server=${KUBE_APISERVER} 
  --kubeconfig=kube-proxy.kubeconfig
kubectl config set-credentials kube-proxy 
  --client-certificate=kube-proxy.pem 
  --client-key=kube-proxy-key.pem 
  --embed-certs=true 
  --kubeconfig=kube-proxy.kubeconfig
kubectl config set-context default 
  --cluster=kubernetes 
  --user=kube-proxy 
  --kubeconfig=kube-proxy.kubeconfig
kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig

# 生成高级审计配置
cat >> audit-policy.yaml <<EOF
# Log all requests at the Metadata level.
apiVersion: audit.k8s.io/v1beta1
kind: Policy
rules:
- level: Metadata
EOF

3.2 分发 rpm 及证书

创建好证书以后就要进行分发,同时由于 Master 也作为 Node 使用,所以以下命令中在 Master 上也安装了 kubelet、kube-proxy 组件

# 分发并安装 rpm
for IP in `seq 5 7`; do
    scp kubernetes*.rpm [email protected].$IP:~; 
    ssh [email protected].$IP yum install -y kubernetes*.rpm
done

# 分发证书
for IP in `seq 5 7`;do
    ssh [email protected].$IP mkdir /etc/kubernetes/ssl
    scp *.pem [email protected].$IP:/etc/kubernetes/ssl
    scp *.kubeconfig token.csv audit-policy.yaml [email protected].$IP:/etc/kubernetes
    ssh [email protected].$IP chown -R kube:kube /etc/kubernetes/ssl
done

# 设置 log 目录权限
for IP in `seq 5 7`;do
    ssh [email protected].$IP mkdir -p /var/log/kube-audit /usr/libexec/kubernetes
    ssh [email protected].$IP chown -R kube:kube /var/log/kube-audit /usr/libexec/kubernetes
    ssh [email protected].$IP chmod -R 755 /var/log/kube-audit /usr/libexec/kubernetes
done

3.3 搭建 Master 节点

证书与 rpm 都安装完成后,只需要修改配置(配置位于 /etc/kubernetes 目录)后启动相关组件即可

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

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

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

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

apiserver 配置

###
# kubernetes system config
#
# The following values are used to configure the kube-apiserver
#

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

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

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

# Comma separated list of nodes in the etcd cluster
KUBE_ETCD_SERVERS="--etcd-servers=https://10.10.1.5:2379,https://10.10.1.6:2379,https://10.10.1.7:2379"

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

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

# Add your own!
KUBE_API_ARGS="--authorization-mode=RBAC,Node 
               --anonymous-auth=false 
               --kubelet-https=true 
               --enable-bootstrap-token-auth 
               --token-auth-file=/etc/kubernetes/token.csv 
               --service-node-port-range=30000-50000 
               --tls-cert-file=/etc/kubernetes/ssl/kubernetes.pem 
               --tls-private-key-file=/etc/kubernetes/ssl/kubernetes-key.pem 
               --client-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
               --service-account-key-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
               --etcd-quorum-read=true 
               --storage-backend=etcd3 
               --etcd-cafile=/etc/etcd/ssl/etcd-root-ca.pem 
               --etcd-certfile=/etc/etcd/ssl/etcd.pem 
               --etcd-keyfile=/etc/etcd/ssl/etcd-key.pem 
               --enable-swagger-ui=true 
               --apiserver-count=3 
               --audit-policy-file=/etc/kubernetes/audit-policy.yaml 
               --audit-log-maxage=30 
               --audit-log-maxbackup=3 
               --audit-log-maxsize=100 
               --audit-log-path=/var/log/kube-audit/audit.log 
               --event-ttl=1h"

注意:API SERVER 对比 1.7 配置出现几项变动:

  • 移除了 –runtime-config=rbac.authorization.k8s.io/v1beta1 配置,因为 RBAC 已经稳定,被纳入了 v1 api,不再需要指定开启

  • –authorization-mode 授权模型增加了 Node 参数,因为 1.8 后默认 system:node role 不会自动授予 system:nodes 组,具体请参看 CHANGELOG(before-upgrading 段最后一条说明)

  • 由于以上原因,–admission-control 同时增加了 NodeRestriction 参数,关于关于节点授权器请参考 Using Node Authorization

  • 增加 –audit-policy-file 参数用于指定高级审计配置,具体可参考 CHANGELOG(before-upgrading 第四条)、Advanced audit

  • 移除 –experimental-bootstrap-token-auth 参数,更换为 –enable-bootstrap-token-auth,详情参考 CHANGELOG(Auth 第二条)

controller-manager 配置

###
# The following values are used to configure the kubernetes controller-manager

# defaults from config and apiserver should be adequate

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

scheduler 配置

###
# kubernetes scheduler config

# default config should be adequate

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

最后启动 Master 相关组件并验证

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

未分类

四、搭建 Node 节点

4.1 分发 rpm 及证书

对于 Node 节点,只需要安装 kubernetes-node 即可,同时为了方便使用,这里也安装了 kubernetes-client,如下

for IP in `seq 8 9`;do
    scp kubernetes-node-1.8.0-1.el7.centos.x86_64.rpm kubernetes-client-1.8.0-1.el7.centos.x86_64.rpm [email protected].$IP:~
    ssh [email protected].$IP yum install -y kubernetes-node-1.8.0-1.el7.centos.x86_64.rpm kubernetes-client-1.8.0-1.el7.centos.x86_64.rpm
done

同时还要分发相关证书;这里将 Etcd 证书已进行了分发,是因为 虽然 Node 节点上没有 Etcd,但是如果部署网络组件,如 calico、flannel 等时,网络组件需要联通 Etcd 就会用到 Etcd 的相关证书。

# 分发 Kubernetes 证书
for IP in `seq 8 9`;do
    ssh [email protected].$IP mkdir /etc/kubernetes/ssl
    scp *.pem [email protected].$IP:/etc/kubernetes/ssl
    scp *.kubeconfig token.csv audit-policy.yaml [email protected].$IP:/etc/kubernetes
    ssh [email protected].$IP chown -R kube:kube /etc/kubernetes/ssl
done

# 分发 Etcd 证书
for IP in `seq 8 9`;do
    ssh [email protected].$IP mkdir -p /etc/etcd/ssl
    scp *.pem [email protected].$IP:/etc/etcd/ssl
    ssh [email protected].$IP chmod -R 644 /etc/etcd/ssl/*
    ssh [email protected].$IP chmod 755 /etc/etcd/ssl
done

4.2 修改 Node 配置

Node 上只需要修改 kubelet 和 kube-proxy 的配置即可

config 通用配置

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

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

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

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

kubelet 配置

###
# kubernetes kubelet (minion) config

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

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

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

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

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

注意: kubelet 配置与 1.7 版本有一定改动

  • 增加 –fail-swap-on=false 选项,否则可能导致在开启 swap 分区的机器上无法启动 kubelet,详细可参考 CHANGELOG(before-upgrading 第一条)

  • 移除 –require-kubeconfig 选项,已经过时废弃

proxy 配置

###
# kubernetes proxy config
# default config should be adequate
# Add your own!
KUBE_PROXY_ARGS="--bind-address=10.10.1.8 
                 --hostname-override=docker4.node 
                 --kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig 
                 --cluster-cidr=10.254.0.0/16"

kube-proxy 配置与 1.7 并无改变,最新 1.8 的 ipvs 模式将单独写一篇文章,这里不做介绍

4.3 创建 Nginx 代理

由于 HA 方案基于 Nginx 反代实现,所以每个 Node 要启动一个 Nginx 负载均衡 Master,具体参考 HA Master 简述

nginx.conf

# 创建配置目录
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.10.1.5:6443;
        server 10.10.1.6:6443;
        server 10.10.1.7:6443;
    }

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

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

nginx-proxy.service

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

[Service]
User=root
PermissionsStartOnly=true
ExecStart=/usr/bin/docker run -p 127.0.0.1:6443:6443 \
                              -v /etc/nginx:/etc/nginx \
                              --name nginx-proxy \
                              --net=host \
                              --restart=on-failure:5 \
                              --memory=512M \
                              nginx:1.13.5-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

4.4 添加 Node

一切准备就绪后就可以添加 Node 了,首先由于我们采用了 TLS Bootstrapping,所以需要先创建一个 ClusterRoleBinding

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

然后启动 kubelet

systemctl daemon-reload
systemctl start kubelet
systemctl enable kubelet

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

10月 06 19:53:23 docker4.node kubelet[3797]: I1006 19:53:23.917261    3797 bootstrap.go:57] Using bootstrap kubeconfig to generate TLS client cert, key and kubeconfig file

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

kubectl get csr | grep Pending | awk '{print $1}' | xargs kubectl certificate approve

此时可以看到 Node 已经加入了

docker1.node ➜  ~ kubectl get node
NAME           STATUS    ROLES     AGE       VERSION
docker4.node   Ready     <none>    14m       v1.8.0
docker5.node   Ready     <none>    3m        v1.8.0

最后再启动 kube-proxy 即可

systemctl start kube-proxy
systemctl enable kube-proxy

再次提醒: 如果 kubelet 启动出现了类似 system:node:xxxx 用户没有权限访问 API 的 RBAC 错误,那么一定是 API Server 授权控制器、准入控制配置有问题,请仔细阅读上面的文档进行更改

4.5 Master 作为 Node

如果想讲 Master 也作为 Node 的话,请在 Master 上安装 kubernete-node rpm 包,配置与上面基本一致;区别于 Master 上不需要启动 nginx 做负载均衡,同时 bootstrap.kubeconfig、bootstrap.kubeconfig 中的 API Server 地址改成当前 Master IP 即可。

最终成功后如下图所示

未分类

五、部署 Calico

5.1 修改 Calico 配置

Calico 部署仍然采用 “混搭” 方式,即 Systemd 控制 calico node,cni 等由 kubernetes daemonset 安装,具体请参考 Calico 部署踩坑记录(https://mritd.me/2017/07/31/calico-yml-bug/),以下直接上代码

# 获取 calico.yaml
wget https://docs.projectcalico.org/v2.6/getting-started/kubernetes/installation/hosted/calico.yaml

# 替换 Etcd 地址
sed -i 's@.*etcd_endpoints:.*@  etcd_endpoints: "https://10.10.1.5:2379,https://10.10.1.6:2379,https://10.10.1.7:2379"@gi' calico.yaml

# 替换 Etcd 证书
export ETCD_CERT=`cat /etc/etcd/ssl/etcd.pem | base64 | tr -d 'n'`
export ETCD_KEY=`cat /etc/etcd/ssl/etcd-key.pem | base64 | tr -d 'n'`
export ETCD_CA=`cat /etc/etcd/ssl/etcd-root-ca.pem | base64 | tr -d 'n'`

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

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

# 注释掉 calico-node 部分(由 Systemd 接管)
sed -i '103,189s@.*@#&@gi' calico.yaml

5.2 创建 Systemd 文件

上一步注释了 calico.yaml 中 Calico Node 相关内容,为了防止自动获取 IP 出现问题,将其移动到 Systemd,Systemd service 配置如下,每个节点都要安装 calico-node 的 Service,其他节点请自行修改 ip(被问我为啥是两个反引号 \,自己试就知道了)

cat > /usr/lib/systemd/system/calico-node.service <<EOF
[Unit]
Description=calico node
After=docker.service
Requires=docker.service

[Service]
User=root
PermissionsStartOnly=true
ExecStart=/usr/bin/docker run   --net=host --privileged --name=calico-node \
                                -e ETCD_ENDPOINTS=https://10.10.1.5:2379,https://10.10.1.6:2379,https://10.10.1.7:2379 \
                                -e ETCD_CA_CERT_FILE=/etc/etcd/ssl/etcd-root-ca.pem \
                                -e ETCD_CERT_FILE=/etc/etcd/ssl/etcd.pem \
                                -e ETCD_KEY_FILE=/etc/etcd/ssl/etcd-key.pem \
                                -e NODENAME=docker1.node \
                                -e IP=10.10.1.5 \
                                -e IP6= \
                                -e AS= \
                                -e CALICO_IPV4POOL_CIDR=10.20.0.0/16 \
                                -e CALICO_IPV4POOL_IPIP=always \
                                -e CALICO_LIBNETWORK_ENABLED=true \
                                -e CALICO_NETWORKING_BACKEND=bird \
                                -e CALICO_DISABLE_FILE_LOGGING=true \
                                -e FELIX_IPV6SUPPORT=false \
                                -e FELIX_DEFAULTENDPOINTTOHOSTACTION=ACCEPT \
                                -e FELIX_LOGSEVERITYSCREEN=info \
                                -v /etc/etcd/ssl/etcd-root-ca.pem:/etc/etcd/ssl/etcd-root-ca.pem \
                                -v /etc/etcd/ssl/etcd.pem:/etc/etcd/ssl/etcd.pem \
                                -v /etc/etcd/ssl/etcd-key.pem:/etc/etcd/ssl/etcd-key.pem \
                                -v /var/run/calico:/var/run/calico \
                                -v /lib/modules:/lib/modules \
                                -v /run/docker/plugins:/run/docker/plugins \
                                -v /var/run/docker.sock:/var/run/docker.sock \
                                -v /var/log/calico:/var/log/calico \
                                quay.io/calico/node:v2.6.1
ExecStop=/usr/bin/docker rm -f calico-node
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

5.3 修改 kubelet 配置

根据官方文档要求 kubelet 配置必须增加 –network-plugin=cni 选项,所以需要修改 kubelet 配置

###
# kubernetes kubelet (minion) config
# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)
KUBELET_ADDRESS="--address=10.10.1.5"
# The port for the info server to serve on
# KUBELET_PORT="--port=10250"
# You may leave this blank to use the actual hostname
KUBELET_HOSTNAME="--hostname-override=docker1.node"
# location of the api-server
# KUBELET_API_SERVER=""
# Add your own!
KUBELET_ARGS="--cgroup-driver=cgroupfs 
              --network-plugin=cni 
              --cluster-dns=10.254.0.2 
              --resolv-conf=/etc/resolv.conf 
              --experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig 
              --kubeconfig=/etc/kubernetes/kubelet.kubeconfig 
              --fail-swap-on=false 
              --cert-dir=/etc/kubernetes/ssl 
              --cluster-domain=cluster.local. 
              --hairpin-mode=promiscuous-bridge 
              --serialize-image-pulls=false 
              --pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0"

然后重启即可

systemctl daemon-reload
systemctl restart kubelet

此时执行 kubectl get node 会看到 Node 为 NotReady 状态,属于正常情况

5.4 创建 Calico Daemonset

# 先创建 RBAC
kubectl apply -f https://docs.projectcalico.org/v2.6/getting-started/kubernetes/installation/rbac.yaml

# 再创建 Calico Daemonset
kubectl create -f calico.yaml

5.5 创建 Calico Node

Calico Node 采用 Systemd 方式启动,在每个节点配置好 Systemd service后,每个节点修改对应的 calico-node.service 中的 IP 和节点名称,然后启动即可

systemctl daemon-reload
systemctl restart calico-node
sleep 5
systemctl restart kubelet

此时检查 Node 应该都处于 Ready 状态

未分类

最后测试一下跨主机通讯

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

进入其中一个 Pod,ping 另一个 Pod 的 IP 测试即可

未分类

六、部署 DNS

6.1 部署集群 DNS

DNS 组件部署非常简单,直接创建相应的 deployment 等即可;但是有一个事得说一嘴,Kubernets 一直在推那个 Addon Manager 的工具来管理 DNS 啥的,文档说的条条是道,就是不希望我们手动搞这些东西,防止意外修改云云… 但问题是关于那个 Addon Manager 咋用一句没提,虽然说里面就一个小脚本,看看也能懂;但是我还是选择手动 ????… 还有这个 DNS 配置文件好像又挪地方了,以前在 contrib 项目下的…

# 获取文件
wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kube-dns.yaml.sed
mv kube-dns.yaml.sed kube-dns.yaml

# 修改配置
sed -i 's/$DNS_DOMAIN/cluster.local/gi' kube-dns.yaml
sed -i 's/$DNS_SERVER_IP/10.254.0.2/gi' kube-dns.yaml

# 创建
kubectl create -f kube-dns.yaml

创建好以后如下所示

未分类

然后创建两组 Pod 和 Service,进入 Pod 中 curl 另一个 Service 名称看看是否能解析;同时还要测试一下外网能否解析

未分类

测试外网

未分类

6.2 部署 DNS 自动扩容部署

这个同样下载 yaml,然后创建一下即可,不需要修改任何配置

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

部署完成后如下

未分类

自动扩容这里不做测试了,虚拟机吃不消了,详情自己参考 https://kubernetes.io/docs/tasks/administer-cluster/dns-horizontal-autoscaling/

Kubernetes之“暂停”容器

【编者的话】希望这篇文章可以帮助大家更好的了解Kubernetes的相关核心内容。

当检查你的Kubernetes集群的节点时,在节点上执行命令docker ps,你可能会注意到一些被称为“暂停(/pause)”的容器。

$ docker ps
CONTAINER ID IMAGE COMMAND ...
...
3b45e983c859 gcr.io/google_containers/pause-amd64:3.0  “/pause” ...
...
dbfc35b00062 gcr.io/google_containers/pause-amd64:3.0  “/pause” ...
...
c4e998ec4d5d gcr.io/google_containers/pause-amd64:3.0  “/pause” ...
...
508102acf1e7 gcr.io/google_containers/pause-amd64:3.0  “/pause” ...

这些“暂停”容器是啥,而且还这么多暂停的?这到底是什么情况?

未分类

为了回答这些问题,我们需要退一步看看Kubernetes中的pods如何实现,特别是在Docker/containerd运行时。如果你还不知道怎么做,可以先阅读我以前发表的关于Kubernetes pods 的文章(http://dockone.io/article/2682)。

Docker支持容器,这非常适合部署单个软件单元。但是,当你想要一起运行多个软件时,这种模式会变得有点麻烦。当开发人员创建使用supervisord作为入口点的Docker镜像来启动和管理多个进程时,你经常会看到这一点。对于生产系统,许多人发现,将这些应用程序部署在部分隔离并部分共享环境的容器组中更为有用。

Kubernetes为这种使用场景提供了一个称为Pod的干净的抽象。它在隐藏Docker标志的复杂性的同时会保留容器,共享卷等。它也隐藏了容器运行时的差异。例如,rkt原生支持Pod,所以Kubernetes的工作要少一些,但是不管怎样,作为Kubernetes用户的你都不用担心(Pod会帮你搞定这些)。

原则上,任何人都可以配置Docker来控制容器组之间的共享级别——你只需创建一个父容器,在知道正确的标志配置的情况下来设置并创建共享相同环境的新容器,然后管理这些容器的生命周期。而管理所有这些片段的生命周期可能会变得相当复杂。

在Kubernetes中,“暂停”容器用作你的Pod中所有容器的“父容器”。“暂停”容器有两个核心职责。首先,在Pod中它作为Linux命名空间共享的基础。其次,启用PID(进程ID)命名空间共享,它为每个Pod提供PID 1,并收集僵尸进程。

共享命名空间

在Linux中,当你运行新进程时,该进程从父进程继承其命名空间。在新命名空间中运行进程的方法是通过“取消共享”命名空间(与父进程),从而创建一个新的命名空间。以下是使用该unshare工具在新的PID,UTS,IPC和装载命名空间中运行shell的示例。

sudo unshare --pid --uts --ipc --mount -f chroot rootfs / bin / sh

一旦进程运行,你可以将其他进程添加到进程的命名空间中以形成一个Pod。可以使用setns系统调用将新进程添加到现有命名空间。

Pod中的容器在其中共享命名空间。Docker可让你自动执行此过程,因此,让我们来看一下如何使用“暂停”容器和共享命名空间从头开始创建Pod的示例。首先,我们将需要使用Docker启动“暂停”容器,以便我们可以将容器添加到Pod中。

docker run -d --name pause gcr.io/google_containers/pause-amd64:3.0

然后我们可以运行我们的Pod的容器。首先我们将运行Nginx。这将在端口2368上设置Nginx到其localhost的代理请求。

$ cat <<EOF >> nginx.conf
> error_log stderr;
> events { worker_connections  1024; }
> http {
>     access_log /dev/stdout combined;
>     server {
>         listen 80 default_server;
>         server_name example.com www.example.com;
>         location / {
>             proxy_pass http://127.0.0.1:2368;
>         }
>     }
> }
> EOF
$ docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf -p 8080:80 --net=container:pause --ipc=container:pause --pid=container:pause nginx 

然后,我们将为作为我们的应用服务器的ghost博客应用程序创建另一个容器。

$ docker run -d --name ghost --net = container:pause --ipc = container:pause --pid = container:pause ghost

在这两种情况下,我们将“暂停”容器指定为我们要加入其命名空间的容器。这将有效地创建我们的Pod。如果你访问 http://localhost:8080/ 你应该能够看到ghost通过Nginx代理运行,因为网络命名空间在pause,nginx和ghost容器之间共享。

未分类

如果你觉得这一切好复杂,恭喜你,大家都这么就觉得;它确实很复杂(感觉像句废话)。而且我们甚至还没有了解如何监控和管理这些容器的生命周期。不过,值得庆幸的事,通过Pod,Kubernetes会为你很好地管理所有这些。

收割僵尸

在Linux中,PID命名空间中的所有进程会形成一个树结构,每个进程都会有一个父进程。只有在树的根部的进程没有父进程。这个进程就是“init”进程,即PID 1。

进程可以使用fork和exec syscalls启动其他进程。当启动了其他进程,新进程的父进程就是调用fork syscall的进程。fork用于启动正在运行的进程的另一个副本,而exec则用于启动不同的进程。每个进程在OS进程表中都有一个条目。这将记录有关进程的状态和退出代码。当子进程运行完成,它的进程表条目仍然将保留直到父进程使用wait syscall检索其退出代码将其退出。这被称为“收割”僵尸进程。

未分类

僵尸进程是已停止运行但进程表条目仍然存在的进程,因为父进程尚未通过wait syscall进行检索。从技术层面来说,终止的每个进程都算是一个僵尸进程,尽管只是在很短的时间内发生的,但只要不终止他们就可以存活更久。

当父进程wait在子进程完成后不调用syscall时,会发生较长的生存僵尸进程。这样的情况有很多,比如:当父进程写得不好并且简单地省略wait call时,或者当父进程在子进程之前死机,并且新的父进程没有调用wait去检索子进程时。当进程的父进程在子进程之前死机时,OS将子进程分配给“init”进程即PID 1。init进程“收养”子进程并成为其父进程。这意味着现在当子进程退出新的父进程(init)时,必须调用wait 来获取其退出代码否则其进程表项将保持永远,并且它也将成为一个僵尸进程。

在容器中,一个进程必须是每个PID命名空间的init进程。使用Docker,每个容器通常都有自己的PID命名空间,ENTRYPOINT进程是init进程。然而,正如我在上一篇关于Kubernetes Pods的文章 (http://dockone.io/article/2682) 中所指出的,某个容器可以在另一个容器的命名空间中运行。在这种情况下,这个容器必须承担init进程的角色,而其他容器则作为init进程的子进程添加到命名空间中。

在Kubernetes Pods的文章中,我在一个容器中运行Nginx,并将ghost添加到了Nginx容器的PID命名空间。

$ docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf -p 8080:80 nginx
$ docker run -d --name ghost --net=container:nginx --ipc=container:nginx --pid=container:nginx ghost

在这种情况下,Nginx将承担PID 1的作用,并将ghost添加为Nginx的子进程。虽然这样貌似不错,但从技术上来看,Nginx现在需要负责任何ghost进程的子进程。例如,如果ghost分身或者使用子进程运行exec,并在子进程完成之前崩溃,那么这些子进程将被Nginx收养。但是,Nginx并不是设计用来作为一个init进程运行并收割僵尸进程的。这意味着将会有很多的这种僵尸进程,并且在整个容器的生命周期,他们都将持续存活。

在Kubernetes Pods中,容器的运行方式与上述方式大致相同,但是每个Pod都有一个特殊的“暂停”容器。这个“暂停”容器运行一个非常简单的进程,它不执行任何功能,基本上是永远睡觉的(见pause()下面的调用)。因为它比较简单,在这里写下完整的源代码,如下:

/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
include <signal.h>

include <stdio.h>

include <stdlib.h>

include <sys/types.h>

include <sys/wait.h>

include <unistd.h>

static void sigdown(int signo) {
psignal(signo, "Shutting down, got signal");
exit(0);
}

static void sigreap(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main() {
if (getpid() != 1)
/* Not an error because pause sees use outside of infra containers. */
fprintf(stderr, "Warning: pause should be the first processn");

if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 1;
if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 2;
if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,
                                         .sa_flags = SA_NOCLDSTOP},
            NULL) < 0)
return 3;

for (;;)
pause();
fprintf(stderr, "Error: infinite loop terminatedn");
return 42;
} 

正如你所看到的,它当然不会只知道睡觉。它执行另一个重要的功能——即它扮演PID 1的角色,并在子进程被父进程孤立的时候通过调用wait 来收割这些僵尸子进程(参见sigreap)。这样我们就不用担心我们的Kubernetes Pods的PID命名空间里会堆满僵尸了。

PID命名空间共享的一些上下文

值得注意的是,PID命名空间共享已经有了很多的前后关系。如果你启用了PID命名空间共享,那么只能通过暂停容器来收割僵尸,并且目前这一功能仅在Kubernetes 1.7+以上的版本中可用。如果使用Docker 1.13.1+运行Kubernetes 1.7,这一功能默认是开启的,除非使用kubelet标志(–docker-disable-shared-pid=true)禁用。这在Kubernetes 1.8 中正好相反的,现在默认情况下是禁用的,除非由kubelet标志(–docker-disable-shared-pid=false)启用。感兴趣的话,可以看看在GitHub issue中对增加支持PID命名空间共享的有关讨论。

如果没有启用PID命名空间共享,则Kubernetes Pod中的每个容器都将具有自己的PID 1,并且每个容器将需要收集僵尸进程本身。很多时候,这不是一个问题,因为应用程序不会产生其他进程,但僵尸进程使用内存是一个经常被忽视的问题。因此,由于PID命名空间共享使你能够在同一个Pod中的容器之间发送信号,我衷心的希望PID命名空间共享可以成为Kubernetes中的默认选项。

k8s实战-创建ConfigMap

k8s的ConfigMap用来保存配置数据,以键值对形式存储,既可以保存单个属性,也可以保存配置文件。使用ConfigMao前请确保已经安装好了k8s集群,在master主机上执行kubectl create configmap –help,可以看到该命令的使用方法kubectl create configmap map-name map-source。

kubectl create configmap my-config –from-file=path/to/dir

  
该命令以文件目录为源创建ConfigMap,key为文件名,value为文件内容,子文件夹及其下文件将被忽略,例如,k8s-cfg文件夹下有4个文件,文件结构及内容为:

[root@niuhp-vm tmp]# cat k8s-cfg/dir1/file4.data
i am in dir1
[root@niuhp-vm tmp]# cat k8s-cfg/file1
abcdefg
[root@niuhp-vm tmp]# cat k8s-cfg/file2.text
1234567
[root@niuhp-vm tmp]# cat k8s-cfg/file3.log
k1=adsdf,k2=23424,k3=35434

在控制台执行kubectl create configmap my-config-from-dir –from-file=k8s-cfg,成功的话我们会看到如下提示:

[root@niuhp-vm tmp]# create configmap my-config-from-dir --from-file=k8s-cfg
configmap "my-config-from-dir" created

从控制台看下这个ConfigMap的内容

[root@niuhp-vm tmp]# kubectl describe configmap my-config-from-dir
Name:           my-config-from-dir
Namespace:      default
Labels:         <none>
Annotations:    <none>
Data
====
file1:
----
abcdefg
file2.text:
----
1234567
file3.log:
----
k1=adsdf,k2=23424,k3=35434
Events: <none>

从dashboad看下

未分类

另外可以通过参数–namespace={namespace-name}指定命令空间。

kubectl create configmap my-config –from-file=[key1=]/path/to/file1.txt –from-file=[key2=]/path/to/file2.txt

  
该命令以多个文件为源创建ConfigMap,key为文件名(也可以指定),value为文件内容,例如执行create configmap my-config-from-files –from-file=k8s-cfg/file1 –from-file=k8s-cfg/dir1/file4.data创建的ConfigMap为

未分类

执行kubectl create configmap my-config-from-files-custom-key –from-file=mykey1=k8s-cfg/file1 –from-file=mykey2=k8s-cfg/dir1/file4.data创建的ConfigMap为

未分类

kubectl create configmap my-config –from-literal=key1=config1 –from-literal=key2=config2

  
该命令以输入的多个键值对为源创建ConfigMap,例如执行kubectl create configmap my-config-from-kv –from-literal=mykeyzh=nihao –from-literal=mykeyen=hello –from-literal=mykeynum=12345创建的ConfigMap为

未分类

kubectl create configmap my-config –from-env-file=path/to/file

  
该命令以存放键值对的文件为源创建ConfigMap,例如:文件 k8s-test.properties中文件内容如下

[root@niuhp-vm tmp]# cat k8s-test.properties
a=1
b=2
c=3
key2=2sfsdf

执行kubectl create configmap my-config-from-envfile –from-env-file=k8s-test.properties创建的ConfigMap为

未分类