5-3 集群环境搭建_A
5-4 集群环境搭建_B
5-5 集群环境搭建_C
这次开始动手操作,首先是Mesos的安装,怎么来安装Mesos。源码:https://github.com/limingios/msA-docker/mac
https://github.com/limingios/msA-docker/vagrant/Mesos
http://mesos.apache.org/ 本身的安装是c++的,还需要c++进行编译才可以,所以安装感觉比较麻烦,对于安装麻烦的,首选想到的是什么老铁,去dockerhub里面看看,有没有docker的版本。
https://hub.docker.com/u/mesosphere/
咱们应该使用的是单独的master 和单独的slave
server01和server03
docker pull mesosphere/mesos-slave:1.7.0
server02
docker pull mesosphere/mesos-master:1.7.0
zookeeper 启动
#!/bin/bash
cur_dir=`pwd`
docker stop zookeeper
docker rm zookeeper
docker run --name zookeeper --restart always -p 2181:2181 -d zookeeper:3.5
vi mesos.sh
#!/bin/bash
docker run -d --net=host \
--hostname=192.168.66.102 \
-e MESOS_PORT=5050 \
-e MESOS_ZK=zk://192.168.100.139:2181/mesos \
-e MESOS_QUORUM=1 \
-e MESOS_REGISTRY=in_memory \
-e MESOS_LOG_DIR=/var/log/mesos \
-e MESOS_WORK_DIR=/var/tmp/mesos \
-v "$(pwd)/mesos/log/mesos:/var/log/mesos" \
-v "$(pwd)/mesos/tmp/mesos:/var/tmp/mesos" \
mesosphere/mesos-master:1.7.0 --no-hostname_lookup --ip=192.168.66.102
sh mesos.sh
mesos-master 已经连接到了主机zookeeper。
测试连接地址:http://192.168.66.102:5050
Frameworks 未关联,目前还不显示。
Agents 下面的slave的展示
slave容器的创建
vi mesos-slave.sh
#!/bin/bash
docker run -d --net=host --privileged \
--hostname=192.168.66.101 \
-e MESOS_PORT=5051 \
-e MESOS_MASTER=zk://192.168.100.139:2181/mesos \
-e MESOS_SWITCH_USER=0 \
-e MESOS_CONTAINERIZERS=docker,mesos \
-e MESOS_LOG_DIR=/var/log/mesos \
-e MESOS_WORK_DIR=/var/tmp/mesos \
-v "$(pwd)/mesos/log/mesos:/var/log/mesos" \
-v "$(pwd)/mesos/tmp/mesos:/var/tmp/mesos" \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /sys:/sys \
-v /usr/bin/docker:/usr/local/bin/docker \
mesosphere/mesos-slave:1.7.0 --no-systemd_enable_support \
--no-hostname_lookup --ip=192.168.66.101
sh mesos.sh
slave容器的创建
vi mesos-slave.sh
#!/bin/bash
docker run -d --net=host --privileged \
--hostname=192.168.66.103 \
-e MESOS_PORT=5051 \
-e MESOS_MASTER=zk://192.168.100.139:2181/mesos \
-e MESOS_SWITCH_USER=0 \
-e MESOS_CONTAINERIZERS=docker,mesos \
-e MESOS_LOG_DIR=/var/log/mesos \
-e MESOS_WORK_DIR=/var/tmp/mesos \
-v "$(pwd)/mesos/log/mesos:/var/log/mesos" \
-v "$(pwd)/mesos/tmp/mesos:/var/tmp/mesos" \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /sys:/sys \
-v /usr/bin/docker:/usr/local/bin/docker \
mesosphere/mesos-slave:1.7.0 --no-systemd_enable_support \
--no-hostname_lookup --ip=192.168.66.103
https://hub.docker.com/r/mesosphere/marathon,
根据架构图来
docker pull mesosphere/marathon:v1.5.12
vi marathon
#!/bin/bash
docker run -d --net=host \
mesosphere/marathon:v1.5.2 \
--master zk://192.168.100.139:2181/mesos \
--zk zk://192.168.100.139:2181/marathon
网址:http://192.168.66.102:8080
这个是base路径的问题,不影响。
拉取镜像
docker pull mesosphere/marathon-lb:v1.12.3
新建立一个shell 文件
#!/bin/bash
docker stop marathon-lb
docker rm marathon-lb
docker run -d -p 9090:9090 \
-e PORTS=9090 \
mesosphere/marathon-lb:v1.12.3 sse \
--group external \
--marathon http://192.168.66.102:8080
marathon-lb 访问地址:http://localhost:9090/haproxy?stats
while [ true ];do sleep 5;echo 'hello idig8.com';done
在mesos的Framework里面可以看到Marathon的Framework
PS: 所有的服务已经搭建,完毕,下一步就把之前写的6个微服务部署到我们的Mesos集群里面哈哈!
5-6 调整微服务适应Mesos
上次已经搭建了mesos的集群环境,这次看看如何把mesos运行在集群之上,首选需要考虑的问题服务的发现,之前用docker-compose是如何在同一台机器上做的,是不是通过link的名称,link的前提就是需要在同一台主机上,我们当时是在同一台虚拟机上,通过link服务让他们都运行起来,通过名字就可以互相的访问,我们在代码的配置上,也是通过名字让他们彼此之间可以相互的访问,但是现在的情况,我们有2台slave,1台master,我们运行其中任何一个配置的时候都有可能分配到slave中的一个,所以用docker自带的link机制肯定是有问题的。源码:https://github.com/limingios/msA-docker mesos分支
既然服务的机制跟之前的不同的,我们的代码肯定要做下调整。下面我就一起改下。
一种6个微服务。一个一个来吧。
对外提供的9090,它是服务内部的。因为它不需要访问任何服务,不需要修改配置。
里面的mysql是公共组件不需要进行修改,它也不需要依赖任何服务,不需要修改配置。
里面的redis是公共组件不需要进行修改。
通过域名的方式来访问,然后在每个slave机器上绑定一个host
server.name=user-edge-service server.port=8082 thrift.user.ip=lb.idig88.com #thrift.user.ip=127.0.0.1 thrift.user.port=10001 thrift.message.ip=lb.idig88.com thrift.message.port=10002 #redis config spring.redis.host=${redis.address} #spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password=liming spring.redis.timeout=30000
无配置文件不需要考虑。
修改配置文件
server.port=8081 #dubbo config spring.dubbo.application.name=course-dubbo-service spring.dubbo.registry.address=zookeeper://${zookeeper.address}:2181 #spring.dubbo.registry.address=zookeeper://127.0.0.1:2181 spring.dubbo.scan=com.idig8.course user.edge.service.addr=lb.idig88.com:10003
zookeeper 和 mysql 公共组件不需要修改
#dubbo 配置 spring.dubbo.application.name=course-dubbo-service spring.dubbo.registry.address=zookeeper://${zookeeper.address}:2181 #spring.dubbo.registry.address=zookeeper://127.0.0.1:2181 spring.dubbo.protocol.name=dubbo spring.dubbo.protocol.port=20880 #spring.dubbo.protocol.host=127.0.0.1 spring.dubbo.scan=com.idig8.course #数据源的配置 spring.datasource.url=jdbc:mysql://${mysql.address}:3306/db_course #spring.datasource.url=jdbc:mysql://127.0.0.1:3306/db_course spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver thrift.user.ip=lb.idig88.com #thrift.user.ip=127.0.0.1 thrift.user.port=10001
server: port: 8080 spring: application: name: gateway-zuu cloud: gateway: routes: - id: course-edge-service uri: http://lb.idig88.com:10004 predicates: - Path=/course/** - id: user-edge-service uri: http://lb.idig88.com:10003 predicates: - Path=/user/** logging: level: org.springframework.cloud.gateway: debug
一共6个微服务,中间2个服务可以通过的是dubbo来控制的,服务注册给zookeeper,调用者也可以通过zookeeper来获取服务地址,我们就不需要关心服务发现的东西了,所以5个服务就够了。
# 服务名称=新端口/老端口 user-thrift-service=10001/7911 message-thrift-python-service=10002/9000 user.edge.service=10003/8082 course-edge-service=10004/8081 gayway-zuul-service=10005:
PS:代码的微服务调整已经完毕,下一步连接mesos开始部署。
5-7 微服务部署_A
5-8 微服务部署_B
5-9 微服务部署_C
因本人的mac本才8g,无法同时启动4个虚拟机来完成,3个server,1个harbor。所以镜像直接用hub.docker.com的。mac就启动3个虚拟机。代码里都增加dockerhub的方式。源码: github.com/limingios/m… mesos分支
通过提供的源码shell脚本直接运行。
sh start.sh 复制代码
sh start.sh 复制代码
sh start.sh 复制代码
sh start.sh 复制代码
编写的shell 脚本
#!/bin/bash docker run -d --net=host --privileged \ --hostname=192.168.66.101 \ -e MESOS_PORT=5051 \ -e MESOS_MASTER=zk://192.168.1.130:2181/mesos \ -e MESOS_SWITCH_USER=0 \ -e MESOS_CONTAINERIZERS=docker,mesos \ -e MESOS_LOG_DIR=/var/log/mesos \ -e MESOS_WORK_DIR=/var/tmp/mesos \ -v "$(pwd)/mesos/log/mesos:/var/log/mesos" \ -v "$(pwd)/mesos/tmp/mesos:/var/tmp/mesos" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /sys:/sys \ -v /usr/bin/docker:/usr/local/bin/docker \ mesosphere/mesos-slave:1.7.0 --no-systemd_enable_support \ --no-hostname_lookup --ip=192.168.66.101 复制代码
sh mesos-slave.sh 复制代码
编写的shell 脚本 mesos.sh
!/bin/bash docker run -d --net=host \ --hostname=192.168.66.102 \ -e MESOS_PORT=5050 \ -e MESOS_ZK=zk://192.168.1.130:2181/mesos \ -e MESOS_QUORUM=1 \ -e MESOS_REGISTRY=in_memory \ -e MESOS_LOG_DIR=/var/log/mesos \ -e MESOS_WORK_DIR=/var/tmp/mesos \ -v "$(pwd)/mesos/log/mesos:/var/log/mesos" \ -v "$(pwd)/mesos/tmp/mesos:/var/tmp/mesos" \ mesosphere/mesos-master:1.7.0 --no-hostname_lookup --ip=192.168.66.102 复制代码
编写的shell 脚本 marathon.sh
#!/bin/bash docker run -d --net=host \ mesosphere/marathon:v1.5.12 \ --master zk://192.168.1.130:2181/mesos \ --zk zk://192.168.1.130:2181/marathon 复制代码
sh mesos.sh sh marathon.sh 复制代码
编写的shell 脚本 marathon.sh
#!/bin/bash docker run -d --net=host --privileged \ --hostname=192.168.66.103 \ -e MESOS_PORT=5051 \ -e MESOS_MASTER=zk://192.168.1.130:2181/mesos \ -e MESOS_SWITCH_USER=0 \ -e MESOS_CONTAINERIZERS=docker,mesos \ -e MESOS_LOG_DIR=/var/log/mesos \ -e MESOS_WORK_DIR=/var/tmp/mesos \ -v "$(pwd)/mesos/log/mesos:/var/log/mesos" \ -v "$(pwd)/mesos/tmp/mesos:/var/tmp/mesos" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /sys:/sys \ -v /usr/bin/docker:/usr/local/bin/docker \ mesosphere/mesos-slave:1.7.0 --no-systemd_enable_support \ --no-hostname_lookup --ip=192.168.66.103 复制代码
sh mesos-slave.sh 复制代码
memsos配置微服务
我部署下,看我分配的内存和cpu就知道为什么叫微服务了
0.1cpu和128Mb内存足够了。
连接是hub.docker.com,上边说了内存太低了没办法启动那边多镜像。
点击json mode 修改,其实就是修改 json 文件
修改成画红色部分的内容,containerPort是应用内的端口,servicePort是marathon控制的端口(上一节统计过外部端口的数字)。
跟上边一样修改端口
它不需要端口的映射,直接删除portMappings就可以了
key:HAPROXY_GROUP value:external
external 跟原来marathon-lb里面的分组名称一致
本身就是掩饰,家里的笔记本坏了,不好制作局域网的docker仓库,所以比较麻烦。 不过大概就是这么操作的。如果照着我做的老铁,因为我之前跑起来没问题了,建议找个内存大的至少16g的老玩我说的配置。
3种类型
判断是否成功是看命令退出的返回值是不是0
项目的根路径开始的
环境变量的配置
容器的挂载
那些服务适合那些主机标签和权限都在optional里面
PS:基本的mesos和marathon讲述完成了,因为主机内存后面的结果没有演示但是基本的命令和镜像有了后面基本是,基本操作啦!下次一起学学docker swarm!
第6章 服务编排-DockerSwarm
6-1 了解Swarm
内容简介:这次一起了解下docker Swarm,什么是dockerSwarm。下面这个图,就可以看到docker swarm管理docker的一个架构图。以前使用docker命令行是针对docker主机的,然后到这台机器上单独的控制这台机器上的主机,有了swarm之后,客户端命令是针对docker集群的。它的命令几乎等同于docker的原生命令,它把命令发送给swarm,swarm选择发送一个节点去真正的执行,swarm是通过docker自带的远程的API,来实现对docker的控制。
这次一起了解下docker Swarm,什么是dockerSwarm。
使用docker的流程,ssh到一台服务器,运行docker命令来运行本机的docker服务,随着docker发展,越来越多的服务想要运行在docker容器中,如果在这样挨个的登录在每个ssh主机上管理容器,就非常的吃力了,而且我们的应用也需要高可用,也需要避免单点的故障,docker现有的能力已经很难满足这样的需求了,在这样的背景下,docker社区就产生类的dockerSwarm项目。
什么是swarm,swarm这个名词比较贴切,swarm这个单词的意思就是动物的集群行为,比如我们常见的蜂群,鱼群,大雁南飞都可以成为swarm,swarm项目就是把多个docker 实例聚集在一起形成一个大的docker实例,对外提供集群服务,同时这个集群提供所有的api,用户可以相使用docker实例一样使用docker的集群。
昨日今日
>docker swarm在1.12之前是一个独立的项目,需要单独下载,在1.12之后该项目就合并到了docker中,成为docker的子项目,目前docker唯一的一个原生支持docker集群的管理工具。
下面这个图,就可以看到docker swarm管理docker的一个架构图。以前使用docker命令行是针对docker主机的,然后到这台机器上单独的控制这台机器上的主机,有了swarm之后,客户端命令是针对docker集群的。它的命令几乎等同于docker的原生命令,它把命令发送给swarm,swarm选择发送一个节点去真正的执行,swarm是通过docker自带的远程的API,来实现对docker的控制。
下面这个图,这张图跟上面一张图描述的是一个事情,只不过它暴露了更多的的细节,上面的大框和下面的小框都代表了一台服务器,物理机或者虚拟机,我们从上面说,上面是swarm的一个manager节点,管理这个worker节点,可以看到管理了多少个cpu,多少个内存,每个上面运行的服务,每个服务的状况,比如他们的标签,他们的健康状态,Manager管理者每个节点的生命周期,比如加入一个节点,下线一个节点,manager还管理者每个服务的生命周期,服务的部署,更新,停止,删除,Node节点比较单纯他就运行了docker daemon,因为在1.12之后swarm已经融入了docker本身,开发一个远程的api给manager节点调度,运行具体的服务和容器,下面咱们一起看看服务的部署流程是怎样的,在这样的架构上是如何体现的。
想想之前学习的mesos,需要先安装docker,Marathon,zookeeper,加入我们现在有5台liunx服务器,每个上面都装有docker,选择一台作为manager,上面执行下图的第一条命令, 执行完之后会打印出来一个token作为dockerSwarm的凭证,然后在每个worker节点下执行第二条命令,表示要加入集群,只需要token和对应manager节点的ip和端口号,集群环境就搭建完毕了
客户端的发起docker命令,两种方式
docker Client 在manager节点的外边,假如执行了docker service create,先会经过docker Deamon接受这条命令,传给Scheduler模块,Scheduler模块主要实现调度的功能,负责选择出来最优的节点,里面包含了2个子模块,Fiter 和Strategy,Fiter很明显是过滤节点,用来找出满足条件的节点(资源足够多,节点正常的),Strategy是过滤出来后选择出最优的节点(对比选择资源剩余最多的节点,或者找到资源剩余最少的节点),当然Fiter 和Strategy都是用户可以单独定制的,中间的Cluster是抽象的worker节点集群,包含了Swarm节点里面每个节点的信息,右边的Discovery是信息维护的模块,比如Label Health。Cluster最终调用容器的api,完成容器启动的刘而成。
用户在创建服务的时候,选择最优的节点,选择最优节点的管理分为2个阶段。
过滤和策略
filter
约束过滤器,根据当前的操作系统的类型,内核版本,存储的类型进行指标上的约束,也可以自定义约束。当前系统启动的时候可以通过label指定当前机器所具有的特性然后通过Constraints把他们过滤出来。
亲和性过滤器,支持容器的亲和性和镜像的亲和性,比如一个应用,DB容器和web容器放在一起,就可以通过这个来实现,
依赖过滤器,link等等吧Dependency会将这些容器放在同一个节点上,有依赖管理的会将创建的容器和依赖的容器放在同一个节点上。
健康过滤器,根据节点的健康状态进行过滤,把有问题的节点去掉。
端口过滤器,根据端口的使用情况进行过滤,比如一个8080端口在某个主机上被占用,某些主机未被占用,会选用未被占用的那些主机。
Strategy
在同等情况下,会使用资源最多的节点,通过这个策略可以让容器聚集起来。
在同等情况下,会使用资源最少的节点,通过这个策略可以让容器均匀的分布在每个节点上。
随机选择一个节点。
稍微有点复杂,根据场景来说吧
Ingress
>基于物理网络之上的虚拟网络,Swarm的上层应用不在依赖于物理网络,并且能够让下面的物理网络保持不变,老铁就理解到这里就可以了,网络本身涉及到的东西太多了,应该也听过网络工程师,既然有这个职位肯定这个不是那么容易学的,在这里就不会深入的进行详解了。
PS:假定运行了一个 nginx 服务2个实例,nginx1 和nginx2,容器内的端口是80,主机内的端口是8080, 这2个容器分别运行在node2和node3上,看到了吧node1虽然没有运行实例但是依然有8080端口在监听,一个集群在所有的worker节点上都是可以访问到的,随便选一个节点输入它的ip和8080端口就可以访问到,或者搭建一个负载均衡External LB,负责轮询的方式访问每个上边的8080端口,为什么在每个节点上都可以访问我们的服务呢?每个服务启动后所有的节点都会更新自己的VIP LB,把新的服务端口号和服务的信息建立一个关系,VIP LB是基于虚拟IP的负载均衡,VIP LB可以通过虚拟IP解析到真实IP,然后访问到服务。
Ingress+ link
>就类型docker-compose,可以通过docker-compose.yml文件创建出来一组容器,他们之前通过link的方式进行访问,其实这种就类型docker-compose的link网络。
PS:也就是在Ingress之上多了一个link的场景,可以通过link的方式访问,也不需要主机的网络,link怎么实现的呢,如果让一个容器link到另一个容器很容易毕竟他们在一台主机上,一个服务link到另一个服务其实没有那么简单了,可能包含一个容器,也可能包含很多个容器,可能运行在一台机器上,也可能分布在多台机器上,我们如何实现可以通过名字来访问彼此呢,这用到了容器的dns,这里的nginx服务依赖于tomcat服务,nginx有2个实例,tomcat有一个实例,所有的nginx的容器都会对tomcat的解析,把它解析到tomcat的VIP,VIP负责做负载均衡,原理就是这样的原理,link的方式外部是访问不到的。link只适合swarm集群内部的场景。
自定义网络
>使用自定义的网络,首先要创建网络,所有的网络都可以通过名字来连接彼此,而不需要link操作了。只要连接这个网络的彼此,都可以通过名字。底层来说它和link是类型的。通过dns来解析应用的名字。然后通过VIP LB的形式来进行负载均衡。
#创建自定义网络 docker network create --driver=overlay --attachable mynet
#创建服务 docker service create -p 80:80 --network=mynet --name nginx nginx
好处直接可以平滑的切换到docker swarm上。基本不需要改变现有的系统
之前docker的经验可以完成继承过来。
专注docker集群的管理。插件的机制swarm的模块都抽象出来对应的API,可以根据自己的特点进行定制实现。
跟docker同步发布,docker的新的特性在dockerSwarm上都可以得到体现。
PS:docker Swarm基本都了解的差不多了。下次开始docker swarm的环境搭建。
6-2 集群环境搭建(上)
6-3 集群环境搭建(下)
上次了解了docker Swarm,这次一起动手操作,搭建swarm的集群环境,一起测试下三种环境下的服务发现和负载均衡,一个自定义下的overlay下的网络发现,一个Ingress下的服务发现,一个是Ingress+link下的,跟上次Mesos一样我们先建立三台服务虚拟机。 源码:
docker运行状态,切换到root用户
docker version
默认情况下network的结构
docker network ls
查看ip地址
ifconfig
设置manager,初始化manager节点,添加一个work在swarm上,运行下面的命令可以添加work,加入到这个manage中。
docker swarm init --advertise-addr 192.168.66.101
执行添加到manager的命令。
执行添加到manager的命令。
之前是只有三个,一个bridge,一个host,一个none;多了个docker_gwbridge,一个ingress。
ingress的overlay网络,这个网络需要借助docker-gwbridge这个网桥进行连接。如果是
docker network ls
目前的manager只有一个,如果manager节点挂了,这个集群也就挂了。docker swarm 有高可用的状态,就是将server02 和server03 ,manager节点本身也可以提供服务运行的状态,即便我们三个节点都是manager也是可以运行服务的。现在把三个节点都变成manager节点试一下。
docker node ls
升级server02成为manager。变成了Reachable,这个跟zookeeper的原理是一样的,当一个leader挂了之后,通过选取可以产生一个新的leader。
docker node promote docker-swarm-02 docker node promote docker-swarm-03
PS:以上就完成高可用的docker swarm的集群环境,其实真心比其他的简单。
创建一个小的镜像,完成ping www.baidu.com
docker service create --name test1 alpine ping www.baidu.com docker service ls
“` 查看日志
docker service logs test1
![](https://upload-images.jianshu.io/upload_images/11223715-f6ac8c36146450df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
* nginx的环境测试
“` bash
docker service create –name nginx nginx
docker service ls
曾经有老铁问我,如果是容器创建了需要修改创建时候的配置怎么办,我告诉他想给当前容器做个tag打成一个镜像A,然后通过镜像A,生成新的一个新的容器在run里增加你的配置,记住前提是要把原来的容器删除。
对于docker swarm中的service 难道也需要删除?其实不需要直接通过docker service update来完成。
docker service ls
docker service update --publish-add 8080:80 nginx
docker service ls
现在的 nginx 太少了只有1个,如果挂了,还要打电话让人解决。其实还是有办法的。多起几个。可以多等一会,时间就不紧急了,吃了饭再过去。
docker service scale nginx=3
docker service ls
- 默认使用的Ingress下的overlay网络,两个service之前是无法通过service的名称进行访问的。可以试试。
自定义网络添加到docker swarm中。
- 删除刚才创建nginx 和test1
docker service rm nginx test1
- 创建网络
docker network create -d overlay idig8-overlay
-
创建一个service指定网络
>nginx
docker service create --network idig8-overlay --name nginx -p 8080:80 nginx
alpine
docker service create --network idig8-overlay --name alpine alpine ping www.baidu.com
-
查看alpine所在的机器,进入容器,ping nginx
>查看是在server02这台机器上。
docker service ps alpine
进入server02机器,ping 容器名称是nginx的 发现可以ping通
docker ps
docker exec -it 387dd735de74 sh
ping nginx
PS:当前的网络Ingress,容器之间的访问方式可以通过名字访问。在自定义的网络下swarm每个service,都创建一个dnsadress,一定要是自定义的overlay的。
-
dnsrr的方式
>描述是参数不对,在dnsrr下不能开放端口的,他们之间是冲突的。dnsrr是给只通过名字访问。如果不加入overlay的网络它就是独立的。
docker service create --name nginx-b --endpoint-mode dnsrr -p 8090:80 nginx
如果想dnsrr网络可以被访问,可以针对这个service增加overlay的网络
docker service updata --network-add idig8-overlay nginx-b
-
docker stack
>单机模式下,我们可以使用 Docker Compose 来编排多个服务,而在Docker Swarm 通过 Docker Stack 我们只需对已有的 docker-compose.yml 配置文件稍加改造就可以完成 Docker 集群环境下的多服务编排。
version: "3.4"
services:
alpine:
image: alpine
command:
- "ping"
- "www.baidu.com"
networks:
- "idig8-overlay"
deploy:
replicas: 2
restart_policy:
condition: on-failure
resources:
limits:
cpus: "0.1"
memory: 50M
depends_on:
- nginx
nginx:
image: nginx
networks:
- "idig8-overlay"
ports:
- "8080:80"
networks:
idig8-overlay:
external: true
查看stack的命令,上边走的是vip负载均衡的方式而不是dnsrr的方式。
docker stack ls
# -c 文件名 组名
docker stack deploy -c service.yml test
PS:dockerSwarm的服务发现,负载均衡。
6-4 调整微服务及服务配置
上次已经搭建好了swarm的集群环境,server01,server02,server03三台虚拟机,每一台的manager节点也是work节点,首先我们考虑的问题是服务的发现,从微服务的角度考虑,我们有得服务是为了其他服务使用的,如message service,user service,对于swarm上,有的需要暴露端口给其他服务使用,有的是直接通过服务的名称就可以访问的,改造模式,改造代码,然后上传到镜像仓库。最后配置一个docker stack 把他们的关系编写出来,一条命令搞定了。源码:https://github.com/limingios/msA-docker swarm分支
修改微服务的配置
- course-dubbo-service
sh
#!/usr/bin/env bash
source ~/.bash_profile
mvn package
docker build -f ./Dockerfile-hub -t zhugeaming/course-dubbo-service:latest .
docker push zhugeaming/course-dubbo-service:latest
Dockerfile
FROM java:openjdk-8
MAINTAINER liming www.idig8.com
COPY target/course-dubbo-service-1.0-SNAPSHOT.jar /course-dubbo-service.jar
ENTRYPOINT ["java","-jar","/course-dubbo-service.jar"]
- course-edge-service
sh
#!/usr/bin/env bash
source ~/.bash_profile
mvn package
docker build -f ./Dockerfile-hub -t zhugeaming/course-edge-service:latest .
docker push zhugeaming/course-edge-service:latest
Dockerfile
FROM java:openjdk-8
MAINTAINER liming www.idig8.com
COPY target/course-edge-service-1.0-SNAPSHOT.jar /course-edge-service.jar
ENTRYPOINT ["java","-jar","/course-edge-service.jar"]
-
gataway-zuul
>sh
#!/usr/bin/env bash
source ~/.bash_profile
mvn package
docker build -f ./Dockerfile-hub -t zhugeaming/gataway-zuul:latest .
docker push zhugeaming/gataway-zuul:latest
Dockfile
FROM java:openjdk-8
MAINTAINER liming www.idig8.com
COPY target/gataway-zuul-1.0-SNAPSHOT.jar /gataway-zuul.jar
ENTRYPOINT ["java","-jar","/gataway-zuul.jar"]
-
user-edge-service
> sh
#!/usr/bin/env bash
source ~/.bash_profile
mvn package
docker build -f ./Dockerfile-hub -t zhugeaming/user-edge-service:latest .
docker push zhugeaming/user-edge-service:latest
Dockerfile
#!/usr/bin/env bash
source ~/.bash_profile
mvn package
docker build -f ./Dockerfile-hub -t zhugeaming/user-edge-service:latest .
docker push zhugeaming/user-edge-service:latest
-
user-thrift-service
>sh
#!/usr/bin/env bash
source ~/.bash_profile
mvn package
docker build -f ./Dockerfile-hub -t zhugeaming/user-thrift-service:latest .
docker push zhugeaming/user-thrift-service:latest
Dockerfile
FROM java:openjdk-8
MAINTAINER liming www.idig8.com
COPY target/user-thrift-service-1.0-SNAPSHOT.jar /user-thrift-service.jar
ENTRYPOINT ["java","-jar","/user-thrift-service.jar"]
- 编写yml文件 使用docker stack 进行批量生成
version: "3.4"
services:
message-thrift-python-service:
image: zhugeaming/message-thrift-python-service:latest
deploy:
endpoint_mode: dnsrr
resources:
limits:
cpus: "0.2"
memory: "128M"
user-thrift-service:
image: zhugeaming/user-thrift-service:latest
deploy:
endpoint_mode: dnsrr
resources:
limits:
cpus: "0.2"
memory: "512M"
user-edge-service:
image: zhugeaming/user-edge-service:latest
deploy:
endpoint_mode: vip
resources:
limits:
cpus: "0.2"
memory: "512M"
ports:
- "8082:8082"
depends_on:
- user-thrift-service
- message-thrift-python-service
course-dubbo-service:
image: zhugeaming/user-edge-service:latest
deploy:
endpoint_mode: dnsrr
resources:
limits:
cpus: "0.2"
memory: "512M"
depends_on:
- user-thrift-service
course-edge-service:
image: zhugeaming/course-edge-service:latest
deploy:
endpoint_mode: vip
resources:
limits:
cpus: "0.2"
memory: "512M"
ports:
- "8081:8081"
depends_on:
- user-edge-service
gateway-zuul:
image: zhugeaming/gataway-zuul:latest
deploy:
endpoint_mode: vip
resources:
limits:
cpus: "0.2"
memory: "512M"
ports:
- "8080:8080"
depends_on:
- user-edge-service
- course-edge-service
networks:
default:
external:
name: idig8-overlay
docker stack 创建,因为机器内存太小,我还是使用的外网,下载镜像有点慢。
docker stack deploy -c ms-service.yml ms
docker stack services ms
PS:创建成功,下一步就是调试微服务。
6-5 微服务部署
如果我想通过域名访问怎么办,而且这三台主机都有服务。源码:https://github.com/limingios/msA-docker swarm分支
负载均衡
之前讲理论的时候说过负载均衡,对每个服务在每台主机上都是有负载均衡的,来进行轮询,每台主机server01,server02,server03,分别去访问他们的8080端口,去搭建一个负载均衡,可以用 nginx 或者apache,这里使用nginx的方式,毕竟之前使用过nginx。拉取部署完,需要20分钟,外网确实很慢。
- 拉取nginx
docker pull nginx
- 编写一个nginx配置挂载进去
vi nginx.conf
配置文件
upstream idig88 {
server 192.168.66.101:8080;
server 192.168.66.102:8080;
server 192.168.66.103:8080;
}
server {
listen 80;
server_name www.idig88.com;
location / {
proxy_pass http://idig88;
}
}
启动命令
docker run -idt -p 80:80 -v `pwd`/nginx.conf:/etc/nginx/conf.d/default.conf nginx
配置一个host文件,在主机上 不是server01 server02 server03
sudu vi /etc/hosts
登录试试
- 微服务扩缩容
docker service scale ms_gateway-zuul=3
docker service ps ms-gateway-zuul
- 升级某个微服务
docker service update ms_course-edge-service --limit-cpu 0.5
docker service inspect ms_course-edge-service
总结
- 以下各节点常规操作命令,比较简单,就不解释了
#取消manager
docker node demote [NODE]
docker node inspect [NODE]
docker node ls
# 升级成manager
docker node promote [NODE]
docker node ps [NODE]
docker node rm [NODE]
docker node update [OPTIONS] NODE
- yml文件这个很复杂,我建议看看官方的文档更实际一些。
PS:有详细看看官网的文档,里面有非常多样化的配置,我相信基本可以满足大家的需求。到这样docker swarm 也就学完了,相信有个整体的认识吧。另外要说下有2个UI管理docker swarm的工具Portainer和Shipyard。下次开始k8s!
第7章 服务编排-Kubernetes
7-1 了解kubernetes(上)
7-2 了解kubernetes(下)
最后一个服务编排工具的学习k8s。kubernetes其实源于希腊语意思(舵手,领航员)。犹豫不太好挤也不太好写,就有了另一个名称叫k8s,kubernetes是谷歌在2014年开始实施的一个项目,当时google已经有了大规模服务容器管理的经验,内部Borg系统,负责对google内部的一些服务进行调度和管理,它的目的是让用户不必操心资源管理的问题,让他们专注自己的核心业务, 并且最大化数据中心的利用率。
什么是k8s
我们假设有个住户社区,k8s就相当于这个社区的大房东,社区里面有一栋一栋的大楼,大楼可以看做虚拟机器,俗称的VM,大楼里面有很多的住户,每个住户就代表一个pod,那每个住户如何找到他们的位置呢?每个住户如何找到他们的位置就是通过门牌号,我们就理解为IP的位置,在每个住户里面有非常多的家庭成员,爸爸,妈妈,兄弟姐妹,爷爷奶奶,姥爷姥姥,女儿儿子,这些角色就可以理解为container,在这个pod里面的成员,就共享了这个房间里面的资源,水电网络,那些资源就可以把它理解成计算资源,ipu,内存,硬盘。对于大房东k8s,他最主要的功能就是管理,每个住户Pod使用多少资源,那为了就是让整栋大楼,会更有效率的使用很多资源,举例来说:A栋大楼住了太多的住户,太多的Pod,他们直接肯定会相互竞争资源的问题,那它就可以协调某一些pod,就是某一些住户搬到B大楼去,这样会让变得更加的均衡使用。
- k8s
官网:https://kubernetes.io/ k8s是一个自动开源系统,自动化部署,扩缩容,管理容器化的应用。
相比前面的mesos 和swarm,k8s的目的非常的单纯和明确,简单的来说他的目的就是为了服务编排,没有别的。这么明确的明确的目的。虽然目的简单但专注所以专业,非常灵活的使用方式,它考虑到服务服务落地过程中可能遇到的各种各样的问题,各种各样的场景,所以从简单的入手,吃透它。了解它的所有组件,然后回过头看它的架构。
- k8s 集群的样子
这张图简单的描述了,k8s集群的样子,k8s肯定也需要一个集群,服务调度服务编排肯定要有机器,所以需要集群,中间的七边行是Master节点,可以理解为安装了核心组件的,另外的六边形标识的是Node节点,在k8s里面叫worker节点,然后每个节点里面有个kubelet服务和docker服务,
多了2个绿色部分,在master里面Deployment。在Node中就是Containerized app就是容器化的应用。图例就是在Master部署了一个Deployment,在三个节点选中了其中的一个部署了应用。Node中的蓝色圆圈标识的是pod。
pod 是k8s中非常重要的一个概念,所有的应用和服务都是运行在pod里面的,pod是k8s中最小的一个单元,可以理解为k8s的一个原子,pod里面就是容器。
- 第一个pod有独立的IP地址,一个容器
- 第二个pod有独立的Ip地址,一个容器,一个磁盘存储
- 第三个pod有独立的Ip地址,两个容器,一个磁盘存储,这2个容器可以共享IP的,共享网络,共享磁盘的。
- 第三个pod有独立的Ip地址,三个容器,2个的磁盘存储,这3个容器可以共享IP的,共享网络,共享磁盘的。
PS:通过上边的4个小图,可以明白同一个pod里面可以有任意多个容器和存储。
知道了pod运行了容器,pod自己运行在哪里啊。运行在node,通过kubelet来进行的,调度kuelet把pod运行起来。一个node上面可以运行多个pod。只要资源足够可以建立多个pod。
service
- 中间是master节点
- 其余的是node节点
- 下面的这个node,里面运行了一个pod,pod的外边,有一层虚线,虚线标识service,pod的Ip(10.10.10.1),service(10.10.9.1),service和pod的Ip不同,pod是具体运行在一个node上的,如果pod或者node突然挂掉了,编排工具肯定在其他的node节点下重新起一个pod,这个pod肯定的ip也就变了。所以就需要一个serivce的概念,当pod出问题了,产生一个新的pod,新的pod就是一个新的ip,我们就可以通过service的方式找到pod。
- serivce的Ip跟service的生命周期是一致的,如果service不被删除的话,IP一直不发生变化。
- 上边这个2个node,三个pod,其实就是从一个实例变成了3个实例,进行了扩容,对外提供想通的服务,这时这个service,ip就有了另外2个作用,除了可以定位pod的地址,可以对pod地址进行负载均衡,进行轮询,
service的概念基本了解了,怎么确定哪些pod属于一个service,提出一个service的概念,service可能有一个或者是多个pod组成,如何定义service,怎么定义service。在k8s上通过Master的Label Selector的方式,比如s:app=A,s:app=B,有这种标签的确定输入pod等于A 或者pod等于B,所有标签一样的都属于我的小弟。这样service和pod的耦合就非常松。
PS:(梳理概念)pod里面包括N个容器,service里面包括pod,Deployment可能包括service或者是pod。
deployment完成应用扩容
- Master里面发布了一个Deplyment,想给service 进行扩容
- 其实内部是扩容的pod,service只是一个逻辑存在的东西
- 把一组pod形成一个逻辑组就是service,扩容完成后,其他两个节点就有了pod实例。
- service就开始对外的负载均衡endpoint找到对应的pod
滚动更新,停掉了一个旧的pod,启动一个新的pod,这时service既有新的,也有旧的存在,直到所有的旧的都更新完毕才结束。所有的更新和扩容的过程serivce的Ip始终是保持不变的。
k8s的整体架构
首先从整体上看,上边这块就是Master节点,下面有两块都是worker节点,master里面部署的都是k8s的核心模块,虚线框代表的是API Server,提供了资源的核心模块,提供了认证授权和k8s的访问控制,可以通过kubectl或者自己开发的userClient,restApi的形式访问API server。从而完成整个集群的访问。
- ControllerManager负责维护集群的状态,比如故障检测,扩缩容,滚动更新等等。
- Scheduler负责资源的调度,按照预定的策略把pod调度到指定的node节点
- ETCD 用做已执行存储,pod,service的集群等信息,k8s需要持久化的数据都存储在这个上边。
- Kubelet负责维护当前节点上的容器的生命和volumes,网络。
- 每个Node上可以运行一个kube-proxy,负责service 提供内部的服务发现和负载均衡,为service方法做个落地的功能。
- kube-dns负责整个集群的dns服务,这个组件不是必须的,一般通过名字访问比较方便。
- dashboard集群数据的GUI界面。
PS:全过程梳理
- kubectl 发起一个请求,请求经过认证。
- scheduler的策略和评分计算得到目标的node。
- APIServer请求Node,通过kublet把这个Node运行pod起来。
- APIServer把信息发送给ETCD保存起来。
- pod运行起来之后,通过ControllerManager管理每个pod的状态,如果突然挂了,就想办法创建一个pod。给pod分个独立的ip地址,可以在整个集群内使用这个ip来访问它。但是pod的ip是易变的,异常重启和升级的时候,不可能关注某个pod的Ip的。
- 下面虚线的部分表示的是一个service,service里面有3个pod,不在虚线里面的是单独存在的pod,并没有提供service的入口,完成service的具体工作的模块就是kube-proxy,在每个node上都有一个kube-proxy,然后给service分配一个ip,可以访问service里面的pod,所以kube-proxy对应的service都会有一个ip的指向,负载均衡的访问他们。
- kube-proxy(service) 可以把端口和ip直接暴露在node上。外边的请求可以访问node上的ip就可以关联到这个service上了。
- kube-dns 就是为了方便名字直接访问node节点。任何一个pod都可以通过名字来进行访问。
k8s的设计理念
了解设计理念可以更深入的了解k8s,设计实在太好了,非常值得我们学习和借鉴。
- API设计原则
- 所有的api都是声明式的(对于重复的操作是稳定的,所有的对象都是名词,不是动词,用户很容易的期望用户的样子,当前的系统是否满足需求,明确用户的目的,用系统管理的业务意图触发设计)
- 控制机的设计原则(假定各种可能存在错误的可能,并做容错处理,出现局部错误和临时错误是很正常的事情,错误可能存在于物理故障磁盘,外部系统的故障啊,系统本身的代码问题,考虑到任何可能的错误,并且做容错处理,每个模块出现错误后,恢复处理,在系统中不可能保证每个模块始终是连接的,因此任何一个模块都要有自动修复的能力,保证连接不到其他模块而形成的自我崩溃。很多情况下可以做到优雅的降级,要求在设计的过程中,有基本功能和高级功能,同时不会导致高级功能的崩溃,影响到这个模块 的使用,更容易的引入高级功能,而会导致高级功能影响基本功能。)
- k8s网络
- CNI
- Flannel,Calico,Weave
- Pod网络
- scheduler-preselect
- NodiskConflict 挂载冲突
- checkNodeMemMemoryPressure 内存的压力
- Nodeselect 节点的选择器
- FitRescoure CPU,内存的限制
- Affinity 满足pod的连接状态的限制
- scheduler-optimize-select
优先规则,对node进行打分,通过优先函数进行预选规则,每个优先函数可以返回0-10的函数,分数越高,这台主机越适合,对应一个权重。
- selectorSpreadPriority
- LeastRequestedPriority
- AffinityPriority
通过pod的Ip来进行访问
- 不同的node,不同的pod通讯
满足pod,ip不能冲突
k8s的服务发现
- kube-proxy(ClusterIp)
每个服务,所有的pod给虚拟Ip,虚拟Ip只能在内部访问
- kube-proxy(NodePort)
服务暴露到节点,外部的可以通过NodeIp 访问pod
- kube-DNS
负责集群内部的dns解析,内部之间可以通过名称访问pod。
PS:k8s的理论就讲这么多,重点还是实践,下次开始搭建k8s集群
7-3 环境搭建前奏
7-4 预先准备环境
上次讲了k8s的理论部分,从这次开始实践部分。从环境搭建的实际操作中,深入了解k8s的组件和它的架构设计。对于新手来说,搭建一个完整的k8s的环境真心困难啊,至少我在中级搭建的时候感觉很麻烦,如果你科学上网的话,可以通过kubernetes-admin的方式,但是相信大部分的老铁,还是绿色的上网环境,特别k8s的服务器,根本不具备科学上网能力的,对于这个问题,社区也推出来了很多自研的部署方案,经过迭代也踊跃了很多种的方案。都不太适合新手来,虽然好解决,但是安装节点对你来说还是个黑盒,这些方案的学习成本都非常的高,有的不够灵活,想特殊配置的。源码:https://github.com/limingios/msA-docker swarm分支 vagrant/k8s 和
https://github.com/limingios/kubernetes-starter
参考高手总结的k8s的安装
https://github.com/limingios/kubernetes-starter
- 绿色网络情况下愉快的安装网络集群
- 安装的过程加深对k8s的深入理解
尽量让部署变的简单,第一次安装,剥离了认证和授权部分,非必须安装的放在最后,从整体上把握k8s的运行机制。最后加上认证授权。k8s的难点就是认证和授权,加入进去后会让人感觉整个过程,让人感觉非常非常的复杂,所以在这里第一次安装的时候做了剥离。
-
脚本 gen-config.sh
>脚本非常的简单,就是帮助各位老铁生成一些配置文件,因为k8s涉及到的脚本也非常多,不可能手动一点一滴的去敲,配置写成了模板,通过脚本生成适应自己环境的配置文件。比如你的机器的hostname,ip,文件的存放路径各有不同。
预先环境准备
-
虚拟机介绍和安装
>3台虚拟机还是通过vagrant来生成对应的虚拟机。vagrant已经安装了 对应的docker。
系统类型
IP地址
节点角色
CPU
Memory
Hostname
Centos7
192.168.66.101
master
1
2G
server01
Centos7
192.168.66.102
worker
1
1G
server02
Centos7
192.168.66.103
worker
1
1G
server03
- 三台机器接受所有ip的数据包转发
$ vi /lib/systemd/system/docker.service
#找到ExecStart=xxx,在这行上面加入一行,内容如下:(k8s的网络需要)
ExecStartPost=/sbin/iptables -I FORWARD -s 0.0.0.0/0 -j ACCEPT
- 三台机器启动服务
$ systemctl daemon-reload
$ service docker start
系统设置(所有节点)
关闭、禁用防火墙(让所有机器之间都可以通过任意端口建立连接)
systemctl stop firewalld
systemctl disable firewalld
#查看状态
systemctl status firewalld
####设置系统参数 – 允许路由转发,不对bridge的数据进行处理
#写入配置文件
$ cat < /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
#生效配置文件
$ sysctl -p /etc/sysctl.d/k8s.conf
配置host文件
#配置host,使每个Node都可以通过名字解析到ip地址
$ vi /etc/hosts
#加入如下片段(ip地址和servername替换成自己的)
192.168.66.101 server01
192.168.66.102 server02
192.168.66.103 server03
准备二进制文件(所有节点)
kubernetes的安装有几种方式,不管是kube-admin还是社区贡献的部署方案都离不开这几种方式:
– 使用现成的二进制文件
直接从官方或其他第三方下载,就是kubernetes各个组件的可执行文件。拿来就可以直接运行了。不管是centos,ubuntu还是其他的 linux 发行版本,只要gcc编译环境没有太大的区别就可以直接运行的。使用较新的系统一般不会有什么跨平台的问题。
- 使用源码编译安装
编译结果也是各个组件的二进制文件,所以如果能直接下载到需要的二进制文件基本没有什么编译的必要性了。
- 使用镜像的方式运行
同样一个功能使用二进制文件提供的服务,也可以选择使用镜像的方式。就像nginx,像mysql,我们可以使用安装版,搞一个可执行文件运行起来,也可以使用它们的镜像运行起来,提供同样的服务。kubernetes也是一样的道理,二进制文件提供的服务镜像也一样可以提供。
从上面的三种方式中其实使用镜像是比较优雅的方案,容器的好处自然不用多说。但从初学者的角度来说容器的方案会显得有些复杂,不那么纯粹,会有很多容器的配置文件以及关于类似二进制文件提供的服务如何在容器中提供的问题,容易跑偏。
所以我们这里使用二进制的方式来部署。二进制文件已经这里备好,大家可以打包下载,把下载好的文件放到每个节点上,放在哪个目录随你喜欢, 放好后最好设置一下环境变量$PATH ,方便后面可以直接使用命令。(科学上网的老铁也可以自己去官网找找)
####[下载地址(kubernetes 1.9.0版本)] (https://pan.baidu.com/s/1bMnqWY)
- 三台机器mac开通远程登录root用户下
#设置 PasswordAuthentication yes
vi /etc/ssh/sshd_config
sudo systemctl restart sshd
-
将下载的k8s上传到linux服务器上
>密码都是vagrant
scp kubernetes-bins.tar.gz [email protected]:~
scp kubernetes-bins.tar.gz [email protected]:~
scp kubernetes-bins.tar.gz [email protected]:~
-
解压k8s,改名
>解压后,改名成bin就是为了不在配置环境变量
tar -xvf kubernetes-bins.tar.gz
mv kubernetes-bins/ bin
准备配置文件(所有节点)
上一步我们下载了kubernetes各个组件的二进制文件,这些可执行文件的运行也是需要添加很多参数的,包括有的还会依赖一些配置文件。现在我们就把运行它们需要的参数和配置文件都准备好。
下载配置文件
#安装git
yum -y install git
#到home目录下载项目
git clone https://github.com/limingios/kubernetes-starter.git
#看看git内容
cd ~/kubernetes-starter && ll
文件说明
- gen-config.sh
shell脚本,用来根据每个老铁自己的集群环境(ip,hostname等),根据下面的模板,生成适合大家各自环境的配置文件。生成的文件会放到target文件夹下。
- kubernetes-simple
简易版kubernetes配置模板(剥离了认证授权)。
适合刚接触kubernetes的老铁,首先会让大家在和kubernetes初次见面不会印象太差(太复杂啦
),再有就是让大家更容易抓住kubernetes的核心部分,把注意力集中到核心组件及组件的联系,从整体上把握kubernetes的运行机制。
- kubernetes-with-ca
在simple基础上增加认证授权部分。大家可以自行对比生成的配置文件,看看跟simple版的差异,更容易理解认证授权的(认证授权也是kubernetes学习曲线较高的重要原因)
- service-config
这个先不用关注,它是我们曾经开发的那些微服务配置。
等我们熟悉了kubernetes后,实践用的,通过这些配置,把我们的微服务都运行到kubernetes集群中。
3台机器生成配置
这里会根据大家各自的环境生成kubernetes部署过程需要的配置文件。
在每个节点上都生成一遍,把所有配置都生成好,后面会根据节点类型去使用相关的配置。
#cd到之前下载的git代码目录
cd ~/kubernetes-starter
#编辑属性配置(根据文件注释中的说明填写好每个key-value)
vi config.properties
#生成配置文件,确保执行过程没有异常信息
#生成简易版本
./gen-config.sh simple
#查看生成的配置文件,确保脚本执行成功
find target/ -type f
执行gen-config.sh常见问题:
1. gen-config.sh: 3: gen-config.sh: Syntax error: “(” unexpected
– bash版本过低,运行:bash -version查看版本,如果小于4需要升级
– 不要使用 sh gen-config.sh的方式运行(sh和bash可能不一样哦)
2. config.properties文件填写错误,需要重新生成
再执行一次./gen-config.sh simple即可,不需要手动删除target
PS:下一步将一步一步的使用这些文件,到那个时间说说每个文件的功能。
7-5 基础集群部署(上)
基础集群部署 - KUBERNETES-SIMPLE
部署ETCD(主节点)
简介
ETCD保证了数据的存储,保证了数据的高可用,还有数据的一致性,它跟zookeeper类似。kubernetes需要存储很多东西,像它本身的节点信息,组件信息,还有通过kubernetes运行的pod,deployment,service等等。都需要持久化。etcd就是它的数据中心。生产环境中为了保证数据中心的高可用和数据的一致性,一般会部署最少三个节点。我们这里以学习为主就只在主节点部署一个实例。
如果你的环境已经有了etcd服务(不管是单点还是集群),可以忽略这一步。前提是你在生成配置的时候填写了自己的etcd endpoint哦~
部署
etcd的二进制文件和服务的配置我们都已经准备好,现在的目的就是把它做成系统服务并启动。(这个是要在主节点操作的,在server01上)
#把服务配置文件copy到系统服务目录
cp ~/kubernetes-starter/target/master-node/etcd.service /lib/systemd/system/
#enable服务
systemctl enable etcd.service
#创建工作目录(保存数据的地方)
mkdir -p /var/lib/etcd
# 启动服务
service etcd start
# 查看服务日志,看是否有错误信息,确保服务正常
journalctl -f -u etcd.service
# 查看在线的端口2379 2380
netstat -ntlp
查看etcd的配置
WorkingDirectory 工作目录配置文件存在这个路径下
ExecStart 执行的命令
name 名称
listen-client-urls 监听节点
advertise-client-urls 建议其他人访问的地址
data-dir 数据目录
vi /lib/systemd/system/etcd.service
PS: 提示start etcd 已经启动
部署APISERVER(主节点)
简介
kube-apiserver是Kubernetes最重要的核心组件之一,主要提供以下的功能
- 提供集群管理的REST API接口,包括认证授权(我们现在没有用到)数据校验以及集群状态变更等
- 提供其他模块之间的数据交互和通信的枢纽(其他模块通过API Server查询或修改数据,只有API Server才直接操作etcd)
生产环境为了保证apiserver的高可用一般会部署2+个节点,在上层做一个lb做负载均衡,比如haproxy。由于单节点和多节点在apiserver这一层说来没什么区别,所以我们学习部署一个节点就足够啦
部署
APIServer的部署方式也是通过系统服务。部署流程跟etcd完全一样,不再注释
cp kubernetes-starter/target/master-node/kube-apiserver.service /lib/systemd/system/
systemctl enable kube-apiserver.service
service kube-apiserver start
journalctl -f -u kube-apiserver
重点配置说明
[Unit]
Description=Kubernetes API Server
…
[Service]
#可执行文件的位置
ExecStart=/home/michael/bin/kube-apiserver \
#非安全端口(8080)绑定的监听地址 这里表示监听所有地址
–insecure-bind-address=0.0.0.0 \
#不使用https
–kubelet-https=false \
#kubernetes集群的虚拟ip的地址范围
–service-cluster-ip-range=10.68.0.0/16 \
#service的nodeport的端口范围限制
–service-node-port-range=20000-40000 \
#很多地方都需要和etcd打交道,也是唯一可以直接操作etcd的模块
–etcd-servers=http://192.168.1.102:2379 \
…
部署CONTROLLERMANAGER(主节点)
简介
Controller Manager由kube-controller-manager和cloud-controller-manager组成,是Kubernetes的大脑,它通过apiserver监控整个集群的状态,并确保集群处于预期的工作状态。
kube-controller-manager由一系列的控制器组成,像Replication Controller控制副本,Node Controller节点控制,Deployment Controller管理deployment等等
cloud-controller-manager在Kubernetes启用Cloud Provider的时候才需要,用来配合云服务提供商的控制
controller-manager、scheduler和apiserver 三者的功能紧密相关,一般运行在同一个机器上,我们可以把它们当做一个整体来看,所以保证了apiserver的高可用即是保证了三个模块的高可用。也可以同时启动多个controller-manager进程,但只有一个会被选举为leader提供服务。
部署
通过系统服务方式部署
cp ~/kubernetes-starter/target/master-node/kube-controller-manager.service /lib/systemd/system/
systemctl enable kube-controller-manager.service
service kube-controller-manager start
journalctl -f -u kube-controller-manager
重点配置说明
[Unit]
Description=Kubernetes Controller Manager
…
[Service]
ExecStart=/home/michael/bin/kube-controller-manager \
#对外服务的监听地址,这里表示只有本机的程序可以访问它
–address=127.0.0.1 \
#apiserver的url
–master=http://127.0.0.1:8080 \
#服务虚拟ip范围,同apiserver的配置
–service-cluster-ip-range=10.68.0.0/16 \
#pod的ip地址范围
–cluster-cidr=172.20.0.0/16 \
#下面两个表示不使用证书,用空值覆盖默认值
–cluster-signing-cert-file= \
–cluster-signing-key-file= \
…
PS:下次继续把k8s的基础搭建完成。这个坑很大,注意自己的符号标点,我就是把端口签名的冒号写成了点。找了4个小时各种试才发现的。里面有写错的,192.168.1.101.2379 改成192.168.1.101:2379
7-6 基础集群部署(下)
部署SCHEDULER(主节点)
简介
kube-scheduler负责分配调度Pod到集群内的节点上,它监听kube-apiserver,查询还未分配Node的Pod,然后根据调度策略为这些Pod分配节点。我们前面讲到的kubernetes的各种调度策略就是它实现的。
部署
通过系统服务方式部署
cp ~/kubernetes-starter/target/master-node/kube-scheduler.service /lib/systemd/system/
systemctl enable kube-scheduler.service
service kube-scheduler start
journalctl -f -u kube-scheduler
重点配置说明
vi /lib/systemd/system/kube-scheduler.service
[Unit]
Description=Kubernetes Scheduler
…
[Service]
ExecStart=/home/michael/bin/kube-scheduler \
#对外服务的监听地址,这里表示只有本机的程序可以访问它
–address=127.0.0.1 \
#apiserver的url
–master=http://127.0.0.1:8080 \
…
PS:最重要的三个核心组件就部署完了 ,
部署CALICONODE(所有节点)
它是通过系统服务加docker的方式来完成的。
简介
Calico实现了CNI接口,是kubernetes网络方案的一种选择,它一个纯三层的数据中心网络方案(不需要Overlay),并且与OpenStack、Kubernetes、AWS、GCE等IaaS和容器平台都有良好的集成。
Calico在每一个计算节点利用Linux Kernel实现了一个高效的vRouter来负责数据转发,而每个vRouter通过BGP协议负责把自己上运行的workload的路由信息像整个Calico网络内传播——小规模部署可以直接互联,大规模下可通过指定的BGP route reflector来完成。 这样保证最终所有的workload之间的数据流量都是通过IP路由的方式完成互联的。
部署
calico是通过系统服务+docker方式完成的
cp ~/kubernetes-starter/target/all-node/kube-calico.service /lib/systemd/system/
systemctl enable kube-calico.service
service kube-calico start
journalctl -f -u kube-calico
查看配置
vi /lib/systemd/system/kube-calico.service
CALICO可用性验证
查看容器运行情况
docker ps
-
查看节点运行情况
calicoctl node status
查看端口BGP 协议是通过TCP 连接来建立邻居的,因此可以用netstat 命令验证 BGP Peer
netstat -natp|grep ESTABLISHED|grep 179
查看集群ippool情况[主节点]
calicoctl get ipPool -o yaml
5.4 重点配置说明
[Unit]
Description=calico node
…
[Service]
#以docker方式运行
ExecStart=/usr/bin/docker run --net=host --privileged --name=calico-node \
#指定etcd endpoints(这里主要负责网络元数据一致性,确保Calico网络状态的准确性)
-e ETCD_ENDPOINTS=http://192.168.66.101:2379 \
#网络地址范围(同上面ControllerManager)
-e CALICO_IPV4POOL_CIDR=172.20.0.0/16 \
#镜像名,为了加快大家的下载速度,镜像都放到了阿里云上
registry.cn-hangzhou.aliyuncs.com/imooc/calico-node:v2.6.2
配置KUBECTL命令(主节点)
简介
kubectl是Kubernetes的命令行工具,是Kubernetes用户和管理员必备的管理工具。
kubectl提供了大量的子命令,方便管理Kubernetes集群中的各种功能。
初始化
使用kubectl的第一步是配置Kubernetes集群以及认证方式,包括:
- cluster信息:api-server地址
- 用户信息:用户名、密码或**
- Context:cluster、用户信息以及Namespace的组合
我们这没有安全相关的东西,只需要设置好api-server和上下文就好啦:
#指定apiserver地址(ip替换为你自己的api-server地址)
kubectl config set-cluster kubernetes --server=http://192.168.66.101:8080
#指定设置上下文,指定cluster
kubectl config set-context kubernetes --cluster=kubernetes
#选择默认的上下文
kubectl config use-context kubernetes
通过上面的设置最终目的是生成了一个配置文件:~/.kube/config,当然你也可以手写或复制一个文件放在那,就不需要上面的命令了。
配置KUBELET(工作节点102,103这2两台机器)
简介
每个工作节点上都运行一个kubelet服务进程,默认监听10250端口,接收并执行master发来的指令,管理Pod及Pod中的容器。每个kubelet进程会在API Server上注册节点自身信息,定期向master节点汇报节点的资源使用情况,并通过cAdvisor监控节点和容器的资源。
部署
通过系统服务方式部署,但步骤会多一些,具体如下:
#确保相关目录存在
mkdir -p /var/lib/kubelet
mkdir -p /etc/kubernetes
mkdir -p /etc/cni/net.d
#复制kubelet服务配置文件
cp ~/kubernetes-starter/target/worker-node/kubelet.service /lib/systemd/system/
#复制kubelet依赖的配置文件
cp ~/kubernetes-starter/target/worker-node/kubelet.kubeconfig /etc/kubernetes/
#复制kubelet用到的cni插件配置文件
cp ~/kubernetes-starter/target/worker-node/10-calico.conf /etc/cni/net.d/
systemctl enable kubelet.service
service kubelet start
journalctl -f -u kubelet
主节点查看NODE的信息
kubectl get nodes
重点配置说明
kubelet.service
[Unit]
Description=Kubernetes Kubelet
[Service]
#kubelet工作目录,存储当前节点容器,pod等信息
WorkingDirectory=/var/lib/kubelet
ExecStart=/home/michael/bin/kubelet \
#对外服务的监听地址
–address=192.168.66.103 \
#指定基础容器的镜像,负责创建Pod 内部共享的网络、文件系统等,这个基础容器非常重要:K8S每一个运行的 POD里面必然包含这个基础容器,如果它没有运行起来那么你的POD 肯定创建不了
–pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/imooc/pause-amd64:3.0 \
#访问集群方式的配置,如api-server地址等
–kubeconfig=/etc/kubernetes/kubelet.kubeconfig \
#声明cni网络插件
–network-plugin=cni \
#cni网络配置目录,kubelet会读取该目录下得网络配置
–cni-conf-dir=/etc/cni/net.d \
#指定 kubedns 的 Service IP(可以先分配,后续创建 kubedns 服务时指定该 IP),–cluster-domain 指定域名后缀,这两个参数同时指定后才会生效
–cluster-dns=10.68.0.2 \
…
kubelet.kubeconfig
kubelet依赖的一个配置,格式看也是我们后面经常遇到的yaml格式,描述了kubelet访问apiserver的方式
apiVersion: v1
clusters:
- cluster:
#跳过tls,即是kubernetes的认证
insecure-skip-tls-verify: true
#api-server地址
server: http://192.168.1.102:8080
…
calico.conf
calico作为kubernets的CNI插件的配置
{
"name": "calico-k8s-network",
"cniVersion": "0.1.0",
"type": "calico",
"ed_endpoints": "http://192.168.1.102:2379",
"logevel": "info",
"ipam": {
"type": "calico-ipam"
},
"kubernetes": {
"k8s_api_root": "http://192.168.1.102:8080"
}
}
PS:每次安装都需要看日志的,老铁不要认为看日志麻烦,看日志其实是为了避免后续的问题存在,走一步稳一步!后续在出现问题了,就更麻烦。下次老铁我们一起在这个集群上面做下测试和练习。
7-7 小试牛刀
下面我们就来试试看怎么去操作,控制它。我们从最简单的命令开始,尝试一下kubernetes官方的入门教学:playground的内容。了解如何创建pod,deployments,以及查看他们的信息,深入理解他们的关系。源码:https://github.com/limingios/msA-docker k8s分支和https://github.com/limingios/kubernetes-starter 基础集群的搭建查看32节到34节
kubernetes接触命令
-
kubectl version
>查看版本
-
kubectl get nodes
>查看nodes
-
kubectl get pods
>查看pods
-
拉取官方的镜像
>内存有点低比较慢
kubectl run kubernetes-bootcamp --image=jocatalin/kubernetes-bootcamp:v1 --port=8080
- 查看deploy的详细信息
kubectl describe deploy kubernetes-bootcamp
- 查看pods的详细信息
kubectl describe pods kubernetes-bootcamp-6b7849c495-xmmvh
-
如何访问刚才那个8080端口因为它在103那台机器上,可以通过apiserver的方式
>启动2个窗口,一个窗口启动代理命令,
kubectl proxy
然后操作另一个窗口
curl http://localhost:8001/api/v1/proxy/namespaces/default/pods/kubernetes-bootcamp-6b7849c495-xmmvh/
-
扩缩容
>k8s会均匀的使用机器,不会存在一台机器用的很多,一台机器用的很少的情况。
#如果需要从2 变成1 直接还用这个命令
kubectl scale deploy kubernetes-bootcamp --replicas=2
#可以看到在102运行了一个实例,在103在运行了一个实例
kubectl get pods -o wide
-
升级镜像
>目前状态
kubectl describe deploy kubernetes-bootcamp
升级镜像
#原来是v1升级为v2
kubectl set image deploy kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2
kubectl describe deploy kubernetes-bootcamp
deploy的状态
kubectl rollout status deploy kubernetes-bootcamp
回滚镜像
kubectl rollout undo deploy kubernetes-bootcamp
#又变回v1了
kubectl describe deploy kubernetes-bootcamp
删除deploy
kubectl delete deploy kubernetes-bootcamp
配置文件
上边用了比较多的命令了,老铁消化吸收一下,当然一直使用命令管理集群真的很不方便,当拥有大量的应用的时候很难胜任了,k8s也提供了配置文件的方式来管理。跟swarm的有点类似吧。设计和功能上有点区别。
- yaml文件创建pod
mkdir services
cd services
vi nginx-pod.yaml
nginx-pod.yaml 文件
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
命令生成pod
kubectl create -f nginx-pod.yaml
#文件写明的创建pod,就不可能创建deploy
kubectl get deploy
kubectl get pods -o wide
启动代理尝试访问nginx
老套路,一个窗口启动,一个窗口curl
#ip101启动代理
kubectl proxy
#ip101另一个窗口访问
curl http://localhost:8001/api/v1/proxy/namespaces/default/pods/nginx/
- yaml文件创建deployment
vi nginx-Deployment.yaml
nginx-Deployment.yaml 文件
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
命令生成deployment
kubectl create -f nginx-Deployment.yaml
kubectl get deploy
kubectl get pods -o wide
kubectl get pods -l app=nginx
kubectl get pods -l app=nginx -o wide
PS:常用的命令就说到这里吧,下次继续说说service和dns。
7-8 kube-proxy和kube-dns
上次搭建了kubernetes最核心最基础的服务,也学习了一些命令,现在咱们在这个之上proxy和dns,虽然这2个功能非常非常的重要,但是从技术层面他们属于kubernetes的附加组件,可以有也可以没有,如果需要service的功能可以添加kubernetes proxy,如果需要dns通过名字解析服务就需要增加kubernetes dns组件。非必须的功能做成组件的形式,而不是必须安装的形式。这也说明了kubernetes的设计,尽量避免这种强依赖,首先就为集群增加proxy的功能。源码:https://github.com/limingios/msA-docker k8s分支和https://github.com/limingios/kubernetes-starter 基础集群的搭建查看32节到34节
为集群增加service功能 – kube-proxy(工作节点102,103)
简介
每台工作节点上都应该运行一个kube-proxy服务,它监听API server中service和endpoint的变化情况,并通过iptables等来为服务配置负载均衡,是让我们的服务在集群外可以被访问到的重要方式。
部署
通过系统服务方式部署:
#确保工作目录存在
mkdir -p /var/lib/kube-proxy
#复制kube-proxy服务配置文件
cp ~/kubernetes-starter/target/worker-node/kube-proxy.service /lib/systemd/system/
#复制kube-proxy依赖的配置文件
cp ~/kubernetes-starter/target/worker-node/kube-proxy.kubeconfig /etc/kubernetes/
systemctl enable kube-proxy.service
service kube-proxy start
journalctl -f -u kube-proxy
9.3 重点配置说明
kube-proxy.service
[Unit]
Description=Kubernetes Kube-Proxy Server
…
[Service]
#工作目录
WorkingDirectory=/var/lib/kube-proxy
ExecStart=/home/michael/bin/kube-proxy \
#监听地址
–bind-address=192.168.1.103 \
#依赖的配置文件,描述了kube-proxy如何访问api-server
–kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig \
…
102的kube-proxy
[Unit]
Description=Kubernetes Kube-Proxy Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target
[Service]
WorkingDirectory=/var/lib/kube-proxy
ExecStart=/root/bin/kube-proxy \
--bind-address=192.168.66.102 \
--hostname-override=192.168.66.102 \
--kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig \
--logtostderr=true \
--v=2
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
103的kube-proxy
[Unit]
Description=Kubernetes Kube-Proxy Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target
[Service]
WorkingDirectory=/var/lib/kube-proxy
ExecStart=/root/bin/kube-proxy \
--bind-address=192.168.66.103 \
--hostname-override=192.168.66.103 \
--kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig \
--logtostderr=true \
--v=2
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
kube-proxy.kubeconfig
配置了kube-proxy如何访问api-server,内容与kubelet雷同,不再赘述。
操练service
-
查看service
>api的时候就建立的一个service,查看类型是Type:ClusterIp,它有一个虚拟的Ip(10.68.0.1)相当于给apiservice做成了一个服务,一个是集群内的其他组件,可以通过这个ip直接进行访问,不需要依赖具体worker的ip地址了,负载均衡,apiserver的高可用,通过apiserver的ip来完成。上次访问对应的deployment,都是启动代理,然后另一个窗口通过curl的方式来进行访问。
kubectl get services
kubectl describe serivce kubernetes
-
deploy升级成为service
>感觉比较乱,命令去执行的2个端口target-port 和port ,在执行service的时候又出现了一个端口,
- 随机的端口23492节点启动的端口,可以通过端口访问服务
- target-port 这个端口实际启动的端口
- port 虚拟ip下需要访问的端口
kubectl expose deploy kubernetes-bootcamp --type="NodePort" --target-port=8080 --port=80
- worker访问
curl http://192.168.66.103:23492
-
进入容器内部访问的话
>通过docker ps 找到容器id,进入容器exec -it 后
curl http://10.68.99.134:80
都是可以curl通的这是符合k8s的规范的pod之间是互通的。
- 通过yaml文件将pod生成services
vi nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
ports:
- port: 8080
targetPort: 80
nodePort: 20000
selector:
app: nginx
type: NodePort
为集群增加dns功能 – kube-dns(app)
简介
kube-dns为Kubernetes集群提供命名服务,主要用来解析集群服务名和Pod的hostname。目的是让pod可以通过名字访问到集群内服务。它通过添加A记录的方式实现名字和service的解析。普通的service会解析到service-ip。headless service会解析到pod列表。
部署
通过kubernetes应用的方式部署
kube-dns.yaml文件基本与官方一致(除了镜像名不同外)。
里面配置了多个组件,之间使用”—“分隔
#到kubernetes-starter目录执行命令
kubectl create -f target/services/kube-dns.yaml
在特定的命名空间中
kubectl -n kube-system get svc
只要安装了这个后,直接可以通过名称访问
说白了 只要运行dns服务,就可以直接用啦。
PS:基础集群的没有经过认证授权,也就基本完成了,这些组件就是每个k8s公司所必须的,非常重要非常核心,整个集群的搭建让老铁对k8s有个深入的了解,了解每个组件都是干啥用的,让老铁觉得k8s没有那么复杂,通过上边的安装,确实没有mesos和swarm那么简单,但是也没有那么复杂。一定要熟悉k8s的命令。下次了解下认证的授权。
7-9 理解认证、授权
从本节开始完整的kubernetes集群的部署,也就是在前面基础集群的基础上增加了认证和授权,业内对kubernetes的评价的学习曲线陡,不容易入门,很大的原因就是环境的安装和部署,环境的安装和部署的最终原因其中的一半就归功于它的认证和授权。
理解认证授权
为什么要认证
想理解认证,我们得从认证解决什么问题、防止什么问题的发生入手。
防止什么问题呢?是防止有人入侵你的集群,root你的机器后让我们集群依然安全吗?不是吧,root都到手了,那就为所欲为,防不胜防了。
其实网络安全本身就是为了解决在某些假设成立的条件下如何防范的问题。比如一个非常重要的假设就是两个节点或者ip之间的通讯网络是不可信任的,可能会被第三方窃取,也可能会被第三方篡改。就像我们上学时候给心仪的女孩传纸条,传送的过程可能会被别的同学偷看,甚至内容可能会从我喜欢你修改成我不喜欢你了。当然这种假设不是随便想出来的,而是从网络技术现状和实际发生的问题中发现、总结出来的。kubernetes的认证也是从这个问题出发来实现的。
概念梳理
为了解决上面说的问题,kubernetes并不需要自己想办法,毕竟是网络安全层面的问题,是每个服务都会遇到的问题,业内也有成熟的方案来解决。这里我们一起了解一下业内方案和相关的概念。
- 对称加密/非对称加密
这两个概念属于密码学的东西,对于没接触过的老铁不太容易理解。
- 对称加密会对应一系列的加密算法,key把数据加密,必须用同样的key同样的算法可以把明文解出来,速度比较快,但是大家都是用一份明文秘钥来进行的,安全性不好,如果一个人key泄露,就很危险。
- 非对称加密,特点我用一个key把数据加密后,只有用另外一个key才能把他解密,这种算法就是非对称加密算法,特征比较安全,并不需要太多的秘钥,安全性大大的提高。
- SSL/TLS
了解了对称加密和非对称加密后,我们就可以了解一下SSL/TLS了。
- SSL和TLS可以认为一个东西的老版本和新版本
- 它的机制是建立在对称加密和非对称加密的基础之上,做的一层通信协议,建立在传输层之上应用层之下的中间层协议,用来保证传输的安全性和可靠性。
- 先建立非对称加密的方法互相通信,大家达成一个一致,使用随机生成的秘钥进行对称加密的传输,对称加密是不安全,秘钥是不安全的,随机生成生成的秘钥,这个秘钥还不想他人知道,这个秘钥就通过非对称加密的方式通信,进行达成一致,老铁咱们使用某一个字符串作为秘钥,这个会话做成秘钥来进行对称加密进行通信。
什么是授权
授权的概念就简单多了,就是什么人具有什么样的权限,一般通过角色作为纽带把他们组合在一起。也就是一个角色一边拥有多种权限,一边拥有多个人。这样就把人和权限建立了一个关系。
kubernetes的认证授权
Kubernetes集群的所有操作基本上都是通过kube-apiserver这个组件进行的,它提供HTTP RESTful形式的API供集群内外客户端调用。需要注意的是:认证授权过程只存在HTTPS形式的API中。也就是说,如果客户端使用HTTP连接到kube-apiserver,那么是不会进行认证授权的。所以说,可以这么设置,在集群内部组件间通信使用HTTP,集群外部就使用HTTPS,这样既增加了安全性,也不至于太复杂。
对APIServer的访问要经过的三个步骤,前面两个是认证和授权,第三个是 Admission Control,它也能在一定程度上提高安全性,不过更多是资源管理方面的作用。
kubernetes的认证
kubernetes提供了多种认证方式,比如客户端证书、静态token、静态密码文件、ServiceAccountTokens等等。你可以同时使用一种或多种认证方式。只要通过任何一个都被认作是认证通过。下面我们就认识几个常见的认证方式。
- 客户端证书认证
客户端证书认证叫作TLS双向认证,也就是服务器客户端互相验证证书的正确性,在都正确的情况下协调通信加密方案。
为了使用这个方案,api-server需要用–client-ca-file选项来开启。
- 引导Token
当我们有非常多的node节点时,手动为每个node节点配置TLS认证比较麻烦,这时就可以用到引导token的认证方式,前提是需要在api-server开启 experimental-bootstrap-token-auth 特性,客户端的token信息与预先定义的token匹配认证通过后,自动为node颁发证书。当然引导token是一种机制,可以用到各种场景中。
- Service Account Tokens 认证
有些情况下,我们希望在pod内部访问api-server,获取集群的信息,甚至对集群进行改动。针对这种情况,kubernetes提供了一种特殊的认证方式:Service Account。 Service Account 和 pod、service、deployment 一样是 kubernetes 集群中的一种资源,用户也可以创建自己的 Service Account。
ServiceAccount 主要包含了三个内容:namespace、Token 和 CA。namespace 指定了 pod 所在的 namespace,CA 用于验证 apiserver 的证书,token 用作身份验证。它们都通过 mount 的方式保存在 pod 的文件系统中。
kubernetes的授权
在Kubernetes1.6版本中新增角色访问控制机制(Role-Based Access,RBAC)让集群管理员可以针对特定使用者或服务账号的角色,进行更精确的资源访问控制。在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。在一个组织中,角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。
目前 Kubernetes 中有一系列的鉴权机制,因为Kubernetes社区的投入和偏好,相对于其它鉴权机制而言,RBAC是更好的选择。具体RBAC是如何体现在kubernetes系统中的我们会在后面的部署中逐步的深入了解。
2.3 kubernetes的AdmissionControl
AdmissionControl - 准入控制本质上为一段准入代码,在对kubernetes api的请求过程中,顺序为:先经过认证 & 授权,然后执行准入操作,最后对目标对象进行操作。这个准入代码在api-server中,而且必须被编译到二进制文件中才能被执行。
在对集群进行请求时,每个准入控制代码都按照一定顺序执行。如果有一个准入控制拒绝了此次请求,那么整个请求的结果将会立即返回,并提示用户相应的error信息。
常用组件(控制代码)如下:
- AlwaysAdmit:允许所有请求
- AlwaysDeny:禁止所有请求,多用于测试环境
- ServiceAccount:它将serviceAccounts实现了自动化,它会辅助serviceAccount做一些事情,比如如果pod没有serviceAccount属性,它会自动添加一个default,并确保pod的serviceAccount始终存在
- LimitRanger:他会观察所有的请求,确保没有违反已经定义好的约束条件,这些条件定义在namespace中LimitRange对象中。如果在kubernetes中使用LimitRange对象,则必须使用这个插件。
- NamespaceExists:它会观察所有的请求,如果请求尝试创建一个不存在的namespace,则这个请求被拒绝。
PS:这次想说说理论,也理解下,下次直接上手搭建。
7-10 为集群添加认证授权(上)
kubernetes最复杂的就是认证和授权,这次从头搭建另外一套3个虚机的kubernetes,还是通过vagrant来进行搭建,具体vagrant的配置信息查看源码:https://github.com/limingios/msA-docker k8s分支
预先环境准备
- 虚拟机介绍和安装
3台虚拟机还是通过vagrant来生成对应的虚拟机。vagrant已经安装了 对应的docker。
系统类型
IP地址
节点角色
CPU
Memory
Hostname
Centos7
192.168.68.101
master
2
4G
server01
Centos7
192.168.68.102
worker
1
2G
server02
Centos7
192.168.68.103
worker
1
2G
server03
- 三台机器mac开通远程登录root用户下
#设置 PasswordAuthentication yes
vi /etc/ssh/sshd_config
sudo systemctl restart sshd
- 三台机器接受所有ip的数据包转发
vi /lib/systemd/system/docker.service
#找到ExecStart=xxx,在这行上面加入一行,内容如下:(k8s的网络需要)
ExecStartPost=/sbin/iptables -I FORWARD -s 0.0.0.0/0 -j ACCEPT
- 三台机器启动服务
systemctl daemon-reload
service docker restart
系统设置(所有节点)
关闭、禁用防火墙(让所有机器之间都可以通过任意端口建立连接)
systemctl stop firewalld
systemctl disable firewalld
#查看状态
systemctl status firewalld
####设置系统参数 - 允许路由转发,不对bridge的数据进行处理
#写入配置文件
cat < /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
#生效配置文件
sysctl -p /etc/sysctl.d/k8s.conf
配置host文件
#配置host,使每个Node都可以通过名字解析到ip地址
vi /etc/hosts
#加入如下片段(ip地址和servername替换成自己的)
192.168.68.101 server01
192.168.68.102 server02
192.168.68.103 server03
准备二进制文件(所有节点)
kubernetes的安装有几种方式,不管是kube-admin还是社区贡献的部署方案都离不开这几种方式:
- 使用现成的二进制文件
直接从官方或其他第三方下载,就是kubernetes各个组件的可执行文件。拿来就可以直接运行了。不管是centos,ubuntu还是其他的linux发行版本,只要gcc编译环境没有太大的区别就可以直接运行的。使用较新的系统一般不会有什么跨平台的问题。
- 使用源码编译安装
编译结果也是各个组件的二进制文件,所以如果能直接下载到需要的二进制文件基本没有什么编译的必要性了。
- 使用镜像的方式运行
同样一个功能使用二进制文件提供的服务,也可以选择使用镜像的方式。就像nginx,像mysql,我们可以使用安装版,搞一个可执行文件运行起来,也可以使用它们的镜像运行起来,提供同样的服务。kubernetes也是一样的道理,二进制文件提供的服务镜像也一样可以提供。
从上面的三种方式中其实使用镜像是比较优雅的方案,容器的好处自然不用多说。但从初学者的角度来说容器的方案会显得有些复杂,不那么纯粹,会有很多容器的配置文件以及关于类似二进制文件提供的服务如何在容器中提供的问题,容易跑偏。
所以我们这里使用二进制的方式来部署。二进制文件已经这里备好,大家可以打包下载,把下载好的文件放到每个节点上,放在哪个目录随你喜欢,放好后最好设置一下环境变量$PATH,方便后面可以直接使用命令。(科学上网的老铁也可以自己去官网找找)
####[下载地址(kubernetes 1.9.0版本)] (https://pan.baidu.com/s/1bMnqWY)
- 将下载的k8s上传到linux服务器上
密码都是vagrant
yum -y install lrzsz
#选中文件上传就可以了,这次是在windows环境,上次基础搭建是在mac上
rz
- 解压k8s,改名
解压后,改名成bin就是为了不在配置环境变量
tar -xvf kubernetes-bins.tar.gz
mv ~/kubernetes-bins/ bin
准备配置文件(所有节点)
上一步我们下载了kubernetes各个组件的二进制文件,这些可执行文件的运行也是需要添加很多参数的,包括有的还会依赖一些配置文件。现在我们就把运行它们需要的参数和配置文件都准备好。
下载配置文件
#安装git
yum -y install git
#到home目录下载项目
git clone https://github.com/limingios/kubernetes-starter.git
#看看git内容
cd ~/kubernetes-starter && ll
文件说明
- gen-config.sh
shell脚本,用来根据每个老铁自己的集群环境(ip,hostname等),根据下面的模板,生成适合大家各自环境的配置文件。生成的文件会放到target文件夹下。
- kubernetes-simple
简易版kubernetes配置模板(剥离了认证授权)。
适合刚接触kubernetes的老铁,首先会让大家在和kubernetes初次见面不会印象太差(太复杂啦~~),再有就是让大家更容易抓住kubernetes的核心部分,把注意力集中到核心组件及组件的联系,从整体上把握kubernetes的运行机制。
- kubernetes-with-ca
在simple基础上增加认证授权部分。大家可以自行对比生成的配置文件,看看跟simple版的差异,更容易理解认证授权的(认证授权也是kubernetes学习曲线较高的重要原因)
- service-config
这个先不用关注,它是我们曾经开发的那些微服务配置。
等我们熟悉了kubernetes后,实践用的,通过这些配置,把我们的微服务都运行到kubernetes集群中。
3台机器生成配置
这里会根据大家各自的环境生成kubernetes部署过程需要的配置文件。
在每个节点上都生成一遍,把所有配置都生成好,后面会根据节点类型去使用相关的配置。
#cd到之前下载的git代码目录
cd ~/kubernetes-starter
#编辑属性配置(根据文件注释中的说明填写好每个key-value)
vi config.properties
#生成配置文件,确保执行过程没有异常信息
生成配置(所有节点)
跟基础环境搭建一样,我们需要生成kubernetes-with-ca的所有相关配置文件
#生成配置
./gen-config.sh with-ca
PS:这个截图master_ip应该是192.168.68.101
安装cfssl(所有节点)
cfssl是非常好用的CA工具,我们用它来生成证书和秘钥文件
安装过程比较简单,
#下载
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 \
https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
#修改为可执行权限
chmod +x cfssl_linux-amd64 cfssljson_linux-amd64
#移动到bin目录
mv cfssl_linux-amd64 /usr/local/bin/cfssl
mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
#验证
cfssl version
生成根证书(主节点)
根证书是证书信任链的根,各个组件通讯的前提是有一份大家都信任的证书(根证书),每个人使用的证书都是由这个根证书签发的。
#所有证书相关的东西都放在这
mkdir -p /etc/kubernetes/ca
#准备生成证书的配置文件
cp ~/kubernetes-starter/target/ca/ca-config.json /etc/kubernetes/ca
cp ~/kubernetes-starter/target/ca/ca-csr.json /etc/kubernetes/ca
#生成证书和秘钥
cd /etc/kubernetes/ca
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
#生成完成后会有以下文件(我们最终想要的就是ca-key.pem和ca.pem,一个秘钥,一个证书)
ls
改造etcd(主节点)
准备证书
etcd节点需要提供给其他服务访问,就要验证其他服务的身份,所以需要一个标识自己监听服务的server证书,当有多个etcd节点的时候也需要client证书与etcd集群其他节点交互,当然也可以client和server使用同一个证书因为它们本质上没有区别。
#etcd证书放在这
mkdir -p /etc/kubernetes/ca/etcd
#准备etcd证书配置
cp ~/kubernetes-starter/target/ca/etcd/etcd-csr.json /etc/kubernetes/ca/etcd/
cd /etc/kubernetes/ca/etcd/
#使用根证书(ca.pem)签发etcd证书
cfssl gencert \
-ca=/etc/kubernetes/ca/ca.pem \
-ca-key=/etc/kubernetes/ca/ca-key.pem \
-config=/etc/kubernetes/ca/ca-config.json \
-profile=kubernetes etcd-csr.json | cfssljson -bare etcd
#跟之前类似生成三个文件etcd.csr是个中间证书请求文件,我们最终要的是etcd-key.pem和etcd.pem
ls
更新etcd服务:
cp ~/kubernetes-starter/target/master-node/etcd.service /lib/systemd/system/
mkdir -p /var/lib/etcd
systemctl enable etcd.service
systemctl daemon-reload
service etcd start
#验证etcd服务(endpoints自行替换)
ETCDCTL_API=3 etcdctl \
--endpoints=https://192.168.68.101:2379 \
--cacert=/etc/kubernetes/ca/ca.pem \
--cert=/etc/kubernetes/ca/etcd/etcd.pem \
--key=/etc/kubernetes/ca/etcd/etcd-key.pem \
endpoint health
api-server(主节点)
准备证书
#api-server证书放在这,api-server是核心,文件夹叫kubernetes吧,如果想叫apiserver也可以,不过相关的地方都需要修改哦
mkdir -p /etc/kubernetes/ca/kubernetes
#准备apiserver证书配置
cp ~/kubernetes-starter/target/ca/kubernetes/kubernetes-csr.json /etc/kubernetes/ca/kubernetes/
cd /etc/kubernetes/ca/kubernetes/
#使用根证书(ca.pem)签发kubernetes证书
cfssl gencert \
-ca=/etc/kubernetes/ca/ca.pem \
-ca-key=/etc/kubernetes/ca/ca-key.pem \
-config=/etc/kubernetes/ca/ca-config.json \
-profile=kubernetes kubernetes-csr.json | cfssljson -bare kubernetes
#跟之前类似生成三个文件kubernetes.csr是个中间证书请求文件,我们最终要的是kubernetes-key.pem和kubernetes.pem
ll
api-server服务
生成token认证文件
#生成随机token
head -c 16 /dev/urandom | od -An -t x | tr -d ' '
0b1bd95b94caa5534d1d4a7318d51b0e
#按照固定格式写入token.csv,注意替换token内容
echo "0b1bd95b94caa5534d1d4a7318d51b0e,kubelet-bootstrap,10001,\"system:kubelet-bootstrap\"" > /etc/kubernetes/ca/kubernetes/token.csv
更新api-server服务
cp ~/kubernetes-starter/target/master-node/kube-apiserver.service /lib/systemd/system/
systemctl daemon-reload
service kube-apiserver start
#检查日志
journalctl -f -u kube-apiserver
cat /lib/systemd/system/kube-apiserver.service
controller-manager
controller-manager一般与api-server在同一台机器上,所以可以使用非安全端口与api-server通讯,不需要生成证书和私钥。
controller-manager服务
更新controller-manager服务
cd ~/kubernetes-starter/
cp ~/kubernetes-starter/target/master-node/kube-controller-manager.service /lib/systemd/system/
systemctl daemon-reload
service kube-controller-manager start
#检查日志
journalctl -f -u kube-controller-manager
cat /lib/systemd/system/kube-controller-manager.service
scheduler
scheduler一般与apiserver在同一台机器上,所以可以使用非安全端口与apiserver通讯。不需要生成证书和私钥。
scheduler服务
查看diff
比较会发现两个文件并没有区别,不需要改造
cd ~/kubernetes-starter/
cp ~/kubernetes-starter/target/master-node/kube-scheduler.service /lib/systemd/system/
systemctl enable kube-scheduler.service
启动服务
service kube-scheduler start
#检查日志
journalctl -f -u kube-scheduler
cat /lib/systemd/system/kube-scheduler.service
PS:下次开始kubectl,calico,cni,kube-proxy,kube-dns的认证,授权。
7-11 为集群添加认证授权(下)
接上次的继续认证版的k8s搭建。
KUBECTL
准备证书
#kubectl证书放在这,由于kubectl相当于系统管理员,老铁使用admin命名
mkdir -p /etc/kubernetes/ca/admin
#准备admin证书配置 - kubectl只需客户端证书,因此证书请求中 hosts 字段可以为空
cp ~/kubernetes-starter/target/ca/admin/admin-csr.json /etc/kubernetes/ca/admin/
cd /etc/kubernetes/ca/admin/
#使用根证书(ca.pem)签发admin证书
cfssl gencert \
-ca=/etc/kubernetes/ca/ca.pem \
-ca-key=/etc/kubernetes/ca/ca-key.pem \
-config=/etc/kubernetes/ca/ca-config.json \
-profile=kubernetes admin-csr.json | cfssljson -bare admin
#老铁最终要的是admin-key.pem和admin.pem
ls
admin.csr admin-csr.json admin-key.pem admin.pem
8.2 配置KUBECTL
#指定apiserver的地址和证书位置(ip自行修改)
kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/ca/ca.pem \
--embed-certs=true \
--server=https://192.168.68.101:6443
#设置客户端认证参数,指定admin证书和秘钥
kubectl config set-credentials admin \
--client-certificate=/etc/kubernetes/ca/admin/admin.pem \
--embed-certs=true \
--client-key=/etc/kubernetes/ca/admin/admin-key.pem
#关联用户和集群
kubectl config set-context kubernetes \
--cluster=kubernetes --user=admin
#设置当前上下文
kubectl config use-context kubernetes
#设置结果就是一个配置文件,可以看看内容
cat ~/.kube/config
验证master节点
#可以使用刚配置好的kubectl查看一下组件状态
kubectl get componentstatus
CALICO-NODE(主节点生成证书,102,103通过SCP拷贝过去)
准备证书
后续可以看到calico证书用在四个地方:
- calico/node 这个docker 容器运行时访问 etcd 使用证书
- cni 配置文件中,cni 插件需要访问 etcd 使用证书
- calicoctl 操作集群网络时访问 etcd 使用证书
- calico/kube-controllers 同步集群网络策略时访问 etcd 使用证书
#calico证书放在这
mkdir -p /etc/kubernetes/ca/calico
#准备calico证书配置 - calico只需客户端证书,因此证书请求中 hosts 字段可以为空
cp ~/kubernetes-starter/target/ca/calico/calico-csr.json /etc/kubernetes/ca/calico/
cd /etc/kubernetes/ca/calico/
#使用根证书(ca.pem)签发calico证书
cfssl gencert \
-ca=/etc/kubernetes/ca/ca.pem \
-ca-key=/etc/kubernetes/ca/ca-key.pem \
-config=/etc/kubernetes/ca/ca-config.json \
-profile=kubernetes calico-csr.json | cfssljson -bare calico
#老铁最终要的是calico-key.pem和calico.pem
ls
拷贝主节点证书CALICO
由于calico服务是所有节点都需要启动的,需要把这几个文件拷贝到每台服务器上
** 通过主节点拷贝到102,103两台机器上
#root的密码都是vagrant
scp -r /etc/kubernetes/ca/ [email protected]:/etc/kubernetes/ca/
scp -r /etc/kubernetes/ca/ [email protected]:/etc/kubernetes/ca/
确定下主节点的/etc/kubernetes/ca/ 和 102,103内的目录一致。
更新calico服务
cp ~/kubernetes-starter/target/all-node/kube-calico.service /lib/systemd/system/
systemctl daemon-reload
service kube-calico start
#验证calico(能看到其他节点的列表就对啦)
calicoctl node status
KUBELET
老铁这里让kubelet使用引导token的方式认证,所以认证方式跟之前的组件不同,它的证书不是手动生成,而是由工作节点TLS BootStrap 向api-server请求,由主节点的controller-manager 自动签发。
创建角色绑定(主节点)
引导token的方式要求客户端向api-server发起请求时告诉他你的用户名和token,并且这个用户是具有一个特定的角色:system:node-bootstrapper,所以需要先将 bootstrap token 文件中的 kubelet-bootstrap 用户赋予这个特定角色,然后 kubelet 才有权限发起创建认证请求。
在主节点执行下面命令
#可以通过下面命令查询clusterrole列表
kubectl -n kube-system get clusterrole
#可以回顾一下token文件的内容
cat /etc/kubernetes/ca/kubernetes/token.csv
#创建角色绑定(将用户kubelet-bootstrap与角色system:node-bootstrapper绑定)
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper --user=kubelet-bootstrap
创建BOOTSTRAP.KUBECONFIG(102,103工作节点)
这个配置是用来完成bootstrap token认证的,保存了像用户,token等重要的认证信息,这个文件可以借助kubectl命令生成:(也可以自己写配置)
很重要。
0b1bd95b94caa5534d1d4a7318d51b0e
上边有说明这个咋来的
#设置集群参数(注意替换ip)
kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/ca/ca.pem \
--embed-certs=true \
--server=https://192.168.68.101:6443 \
--kubeconfig=bootstrap.kubeconfig
#设置客户端认证参数(注意替换token)
kubectl config set-credentials kubelet-bootstrap \
--token=0b1bd95b94caa5534d1d4a7318d51b0e\
--kubeconfig=bootstrap.kubeconfig
#设置上下文
kubectl config set-context default \
--cluster=kubernetes \
--user=kubelet-bootstrap \
--kubeconfig=bootstrap.kubeconfig
#选择上下文
kubectl config use-context default --kubeconfig=bootstrap.kubeconfig
mkdir -p /var/lib/kubelet
mkdir -p /etc/kubernetes
mkdir -p /etc/cni/net.d
#将刚生成的文件移动到合适的位置
mv bootstrap.kubeconfig /etc/kubernetes/
准备CNI配置(102,103工作节点)
copy配置
cp ~/kubernetes-starter/target/worker-node/10-calico.conf /etc/cni/net.d/
KUBELET服务
更新服务
cp ~/kubernetes-starter/target/worker-node/kubelet.service /lib/systemd/system/
systemctl daemon-reload
service kubelet start
** 登录101主节点输入命令查看状态
kubectl get csr
#启动kubelet之后到master节点允许worker加入(批准worker的tls证书请求)
#--------*在主节点执行*---------
kubectl get csr|grep 'Pending' | awk '{print $1}'| xargs kubectl certificate approve
#-----------------------------
#检查日志
journalctl -f -u kubelet
加入到主节点中。102
103请求加入,102已经加入
KUBE-PROXY(子节点102,103)
准备证书
#proxy证书放在这
mkdir -p /etc/kubernetes/ca/kube-proxy
#准备proxy证书配置 - proxy只需客户端证书,因此证书请求中 hosts 字段可以为空。
#CN 指定该证书的 User 为 system:kube-proxy,预定义的 ClusterRoleBinding system:node-proxy 将User system:kube-proxy 与 Role system:node-proxier 绑定,授予了调用 kube-api-server proxy的相关 API 的权限
cp ~/kubernetes-starter/target/ca/kube-proxy/kube-proxy-csr.json /etc/kubernetes/ca/kube-proxy/
cd /etc/kubernetes/ca/kube-proxy/
#使用根证书(ca.pem)签发calico证书
cfssl gencert \
-ca=/etc/kubernetes/ca/ca.pem \
-ca-key=/etc/kubernetes/ca/ca-key.pem \
-config=/etc/kubernetes/ca/ca-config.json \
-profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy
#老铁最终要的是kube-proxy-key.pem和kube-proxy.pem
ls
生成KUBE-PROXY.KUBECONFIG配置
#设置集群参数(注意替换ip)
kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/ca/ca.pem \
--embed-certs=true \
--server=https://192.168.68.101:6443 \
--kubeconfig=kube-proxy.kubeconfig
#置客户端认证参数
kubectl config set-credentials kube-proxy \
--client-certificate=/etc/kubernetes/ca/kube-proxy/kube-proxy.pem \
--client-key=/etc/kubernetes/ca/kube-proxy/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.kubeconfig
KUBE-PROXY服务
启动服务
mkdir -p /var/lib/kube-proxy
cp ~/kubernetes-starter/target/worker-node/kube-proxy.service /lib/systemd/system/
systemctl daemon-reload
#安装依赖软件
yum -y install conntrack
#启动服务
service kube-proxy start
#查看日志
journalctl -f -u kube-proxy
12. KUBE-DNS
kube-dns有些特别,因为它本身是运行在kubernetes集群中,以kubernetes应用的形式运行。所以它的认证授权方式跟之前的组件都不一样。它需要用到service account认证和RBAC授权。
service account认证:
每个service account都会自动生成自己的secret,用于包含一个ca,token和secret,用于跟api-server认证
RBAC授权:
权限、角色和角色绑定都是kubernetes自动创建好的。老铁只需要创建一个叫做kube-dns的 ServiceAccount即可,官方现有的配置已经把它包含进去了。
准备配置文件
在官方的基础上添加的变量,生成适合老铁我们集群的配置。直接copy就可以啦
cd ~/kubernetes-starter
新的配置没有设定api-server。不访问api-server,它是怎么知道每个服务的cluster ip和pod的endpoints的呢?这就是因为kubernetes在启动每个服务service的时候会以环境变量的方式把所有服务的ip,端口等信息注入进来。
创建KUBE-DNS(主节点101)
kubectl create -f ~/kubernetes-starter/target/services/kube-dns.yaml
#看看启动是否成功
kubectl -n kube-system get pods
PS:终于,安全版的kubernetes集群部署完成了。 涉及到的细节也非常多,就这都对了两篇博文了,如果每个配置都详细解释估计得写本书了。从入门的角度了解认证和授权。
下面老铁们使用新集群先温习一下之前学习过的命令,然后再认识一些新的命令,新的参数,新的功能。
7-12 再试牛刀
安全版的kubernetes集群我们部署完成了。
下面我们使用新集群先温习一下之前学习过的命令,然后再认识一些新的命令,新的参数,新的功能。
熟悉命令
- kubectl version
- kubectl get node
- kubectl get svc
- 运行一个pod
查看pod的日志 kubectl logs kubernetes-bootcamp-6b7849c495-bqc5r -f
kubectl run kubernetes-bootcamp --image=jocatalin/kubernetes-bootcamp:v1 --port=8080
kubectl get pods
kubectl get deploy
kubectl logs kubernetes-bootcamp-6b7849c495-bqc5r -f
- 进入容器
查看pod详情,发现pod里面有对应的挂载点,进入容器内发现挂载点内有证书。
kubectl describe pods
kubectl exec -it kubernetes-bootcamp-6b7849c495-bqc5r bash
- 容器内的这2个文件是怎么来的,crt,namespace,token
就是secrets 里面的值复制给pod,apiserver里面的开启了serviceaccount,会在default的命名空间下,创建一个默认的serviceaccount,在每个pod启动后,会把servicesecret,以文件的形式挂载在pod上。
kubectl get serviceaccount -o json
kubectl get secrets -o yaml
- kubectl apply
nginx-pod.yaml 文件
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
kubectl apply -f nginx-pod.yaml
kubectl get pod
kubectl describe pods nginx
这个比create生成时候多了Anntations。create是删除,在创建。apply是原有的应用的基础上覆盖,可以回滚。
- kubectl 拉取镜像运行本地
kubectl run busybox --rm=true --image=busybox --restart=Never --tty -i
PS:基本就是测试下安全认证的k8s是否可以正常的使用,也使用了几个命令,其实我感觉,kubernetes 跟docker的命令很类似,环境搭建是大头,环境能搭建下,后面的顺水推舟就可以了 。
7-13 部署我们的微服务
第8章 CICD和DevOps
8-1 了解CICD和DevOps
之前已经说了mesos,swarm,k8s都部署了,k8s因为机器的问题,我没做部署,有了微服务和服务编排的基础,我们可以一起了解下CICD和DevOps,之前在中级篇的文章讲过,老铁一起回顾学习下。
它的产生
- 编译失败
从痛苦中产生,小公司人手少。开发人员每天需要处理bug和开发任务,当到达一个阶段的时候,开发人员我说bug修复可以进行测试了,告诉QA,QA进入内网执行部署的脚本,发布到测试环境,告诉我发布失败了,告诉编译有个错误,报错的代码是其他人写的,需要喊过来其他人,看看谁的问题,很快其中一个人说是他写的忘了提交一个类了, 提交代码后告诉我,我告诉QA好了可以重新发布了,起初一两次大家都忍了,后来发现粗心的老铁经常会发生这个或者那样的错误,都有人少提交类或者少提交一个配置导致内网的发布失败,于是就想了一个办法,找了个专用的服务器,每次提交代码的时候,都会触发一个webhook,将代码重新一遍,如果发现编译错误,都会编译对应的代码提交者,这就是最初的持续集成了。(老司机可能都遇见过,其实这个就是最早的持续集成)
- 运行时异常
虽然之前的编译的异常解决了,但是运行时异常又凸显出来了,所以就在这个基础上增加了代码的风格检查,单元测试,覆盖率,加入阿里巴巴编码规范插件啊,这里要吐槽下,据说代码规范都是给外部人看的,内部人都不遵守,其实规范很好,遵守风格统一利于维护,不要挖坑啊老铁。
- 手动发布
新项目,要申请资源,申请端口,配置nginx。老项目也不简单,在二线城市也没运维,stop下线服务。
- 手动部署
上传代码,重启,验证,上线。其实都是重复性的工作,但是这个工作要非常的消息,万一遇见个傻叉rm -rf ,大家都喝西北风了。
- 上边的痛点优化
从细节慢慢的去优化,优化每个环节,为了让流程更顺畅更优雅,这也就是CICD它的由来。
了解CICD和DevOps
CI 是持续集成。CD 是持续部署。
- CI
在传统软件中,集成基本是项目的收尾阶段,我们花费几周或者数月的时间。持续集成就是把集成提前了,搞到了开发阶段,一边开发一边集成。让构建和测试经常反复的一个过程。持续集成一般是多个开发者,为同一个产品同时编写代码。把代码放到一个源数据库的地方,然后开发人员通过一个CI-server的工具进行构建和集成。持续集成首先要求开发者需要自测代码,分别测试各自的代码,保证他们能够正常的工作,测试也成为单元测试,当所有的代码都顺利的测试通过,就认为他们就顺利的集成到一起了。代码的表现也是之前所预计的。好处是使集成不在是一个让人头疼的事情,软件一直在编写集成。在有持续集成之前,软件的开发都是到收尾统一进行的。并不知道它要耗时多久,CI就是让我们的集成融入我们日常的工作中。
- CD
持续部署是建立在持续集成之上的,持续部署就是开发人员在开发和测试代码的时候,同时也在其他环境进行测试这段代码。通常将不同的环境下的部署,叫做部署流水线。我们公司的部署流水线:开发环境,测试环境,准生产环境,生产环境。根据不同的公司,不同的产品,不同的团队而变化,所有的代码会经过前一个测试,才会进入下一个流水线中。通过这种方式,开发人员提交代码后,都是自动的完成的。这个过程叫持续部署。
- DevOps
更好的去优化开发,运维,测试的流程。使开发和运维通过高度自动化的工具,来使得的软件发布和构建更加的快捷频繁可靠。Devops其实是CICD思想的延伸,如果没有CICD其实DevOps就是空中楼阁,没有一点意义,CICD为基础优化开发和运维测试的所有环节。DevOps包含的东西很多,落地方案也是五花八门。
PS:CICD和DevOps有了进一步的认识,下次开始针对CICD做个环境跑跑实践一下。
8-2 准备GitLab和Jenkins
之前说过各家公司的CICD落地方案不同,五花八门,之前说过 java 的微服务,k8s的集群环境,在这位基础,包括代码的编译,代码的提交,单元测试服务的发布,关键的节点自动化起来。源码:https://github.com/limingios/msA-docker/vagrant master分支CICD
Jenkins
java编写的开源的工具,jenkins比较灵活,可以通过插件的方式,添加所需要的插件,除了扩展性还支持多台机器的分布式构建,jenkins的用户群很庞大,可以说是目前最主流的部署工具。
梳理流程git+jenkins+k8s
- 客户端发起代码push到gitlab上
- gitlab配置了webhook的东西,它可以出发jenkins的构建
- jenkins做的事情就比较多
3.1 构建代码
3.2 静态分析
3.3 单元测试
3.4 build镜像
3.5 推送push镜像仓库
3.6 调用k8s的api
- k8s拉取镜像仓库的进行部署。
GitLab安装(101这台主机)
源码中server01
- 下载镜像
$ docker pull gitlab/gitlab-ce:latest
- 运行GitLab容器
使用docker命令运行容器,注意修改hostname为自己喜欢的名字,-v部分挂载目录要修改为自己的目录。
端口映射这里使用的都是安全端口,如果大家的环境没有端口限制或冲突可以使用与容器同端口,如:-p 443:443 -p 80:80 -p 22:22
* 生成启动文件 – start.sh
cat < start.sh
#!/bin/bash
HOST_NAME=gitlab.idig8.com
GITLAB_DIR=`pwd`
docker stop gitlab
docker rm gitlab
docker run -d \\
--hostname \${HOST_NAME} \\
-p 8443:443 -p 8080:80 -p 2222:22 \\
--name gitlab \\
-v \${GITLAB_DIR}/config:/etc/gitlab \\
-v \${GITLAB_DIR}/logs:/var/log/gitlab \\
-v \${GITLAB_DIR}/data:/var/opt/gitlab \\
gitlab/gitlab-ce:latest
EOF
- 运行start.sh 启动gitlab
sh start.sh
-
配置环境
> 修改host文件,使域名可以正常解析
> 192.168.66.101 gitlab.idig8.com
-
修改ssh端口(如果主机端口使用的不是22端口)
> 修改文件:${GITLAB_DIR}/config/gitlab.rb
> 找到这一行:# gitlab_rails[‘gitlab_shell_ssh_port’] = 22
> 把22修改为你的宿主机端口(这里是2222)。然后将注释去掉。
- 重新启动容器
sh start.sh
GitLab试用
地址:http://gitlab.idig8.com:8080/
-
设置管理员密码
>首先根据提示输入管理员密码,这个密码是管理员用户的密码。对应的用户名是root,用于以管理员身份登录Gitlab。
-
创建账号
设置好密码后去注册一个普通账号
-
添加ssh key
>项目建好了,我们加一个ssh key,以后本地pull/push就简单啦
首先去到添加ssh key的页面
然后拿到我们的sshkey 贴到框框里就行啦
怎么拿到呢?看下面:
#先看看是不是已经有啦,如果有内容就直接copy贴过去就行啦
cat ~/.ssh/id_rsa.pub
#如果上一步没有这个文件 我们就创建一个,运行下面命令(邮箱改成自己的哦),一路回车就好了
ssh-keygen -t rsa -C "[email protected]"
cat ~/.ssh/id_rsa.pub
PS:目的是本地push的时候没有权限问题,方便直接提交代码到gitlab上。
-
测试一下
点开我们刚创建的项目,复制ssh的地址
添加个文件试试(我的项目叫microservice)
#clone代码
cd existing_folder
git init
git remote add origin ssh://[email protected]:2222/liming/microservice.git
git add .
git commit -m "Initial commit"
git push -u origin master
Jenkins安装(102这台主机)
源码中server02
- 下载镜像
docker pull stephenreed/jenkins-java8-maven-git
- 运行Jenkins容器
使用docker命令运行容器,注意修改hostname为自己喜欢的名字,-v部分挂载目录要修改为自己的目录。
端口映射这里使用的都是安全端口,如果大家的环境没有端口限制或冲突可以使用与容器同端口,如:-p 443:443 -p 80:80 -p 22:22
* 生成启动文件 – startJenkins.sh
cat < startJenkins.sh
#!/bin/bash
HOST_NAME=jenkins.idig8.com
GITLAB_DIR=/root
docker stop jenkins
docker rm jenkins
docker run -d \
--hostname ${HOST_NAME} \
-p 8888:8080 -p 50000:50000 \
--name jenkins \
-v ${GITLAB_DIR}/jenkins/:/etc/localtime:ro \
-P stephenreed/jenkins-java8-maven-git
EOF
- 运行startJenkins.sh 启动gitlab
sh startJenkins.sh
-
配置环境
> 修改host文件,使域名可以正常解析
> 192.168.66.102 jenkins.idig8.com
- 查看初始化秘钥
docker ps
docker exec -it f3111725cd64 /bin/bash
cat /var/jenkins_home/secrets/initialAdminPassword
页面输入刚才的秘钥
- 选择插件
其他默认,Pipelines全选
涉及到跨域,需要关闭,系统管理-全局安全
PS:可能有的插件安装不了,不要慌老铁,进入到jenkins的管理页面会提示你更新jenkins更新下,然后插件又可以自动下载安装完毕了。
8-3 CICD实践(上)
上节已经安装好了gitlab和jenkins,这次就把CICD的流程串起来切身的体验下CICD。目的就是在gitlab提交代码。触发一系列的流程,最后可以看到新代码的效果(机器内容优先,只做到镜像的打包推送)。源码:https://github.com/limingios/msA-docker/vagrant master分支CICD
了解git代码提交完成jenkins的构建
上次已经把代码上传上去了,进入项目选择settings里面的Integrations
这里可以配置一个url,默认的trigger触发器push的时候,
这里的url地址是哪里来的。请查看jenkins,因为本身gitlab里面的微服务比较多,选择其中的一个服务吧,新建一个任务名称:user-edge-service,允许url远程触发构建任务。
Jenkins中的Job配置里缺少 触发远程构建(例如,使用脚本) 选项的
如图所示的功能没有出现在Job配置页面,这是由于权限问题导致的:
关闭防止跨站点请求伪造
gitlab的Integrations的URL地址修改:
http://192.168.66.102:8888/job/user-edge-service/build?token=123456
gitlab设置Webhooks报错Urlis blocked: Requests to localhost are not allowed。admin 登录设置
gitlab的Integrations,添加完毕。
jenkins的添加完毕
- 测试一把
返回201,说明构建返回成功。
构建说明
咱们的所有构建都是基于pipline的,脚本是用groovy来做的,如果老铁有不会的可以查看,可以流水线语法。
- 开始pipline的编写
Jenkins Pipeline是一套插件,支持将连续输送Pipeline实施和整合到Jenkins。Pipeline 提供了一组可扩展的工具,用于通过Pipeline DSL为代码创建简单到复杂的传送Pipeline 。
写个测试的试试
#!groovy
pipeline {
//之前说过jenkins是支持集群的,但是咱们这里不需要集群的方式,因为有了k8s。any在任何可用的agent 上执行
agent any
//环境变量,
environment {
REPOSITORY="ssh://[email protected]:2222/liming/microservice.git"
}
//流水线是如何提前,都是通过很多个stages下面的stage
stages {
stage('获取代码'){
steps{
echo " start fetch code from git ssh://[email protected]:2222/liming/microservice.git"
deleteDir()
git "${REPOSITORY}"
}
}
}
}
点击立即构建,然后查看效果
jenkins所在容器未配置,秘钥到gitlab上,来一起配置下
进入102的主机上
docker ps
docker exec -it d918e00a583f /bin/bash
ssh-****** -t rsa -C "[email protected]"
cat /root/.ssh/id_rsa.pub
在容器内试试看能git clone 不
在试试,立刻构建
查看目录
需要配置maven仓库地址,当前这个镜像nds国内不识别,在容器内需要操作
echo "nameserver 8.8.8.8" | tee /etc/resolv.conf > /dev/null
apt-get update
apt-get install vim
cd /opt/maven/conf
vi settings.xml
mkdir /usr/lib/jvm/java-8-openjdk-amd64/lib
通过更换maven镜像可以解决此问题,在maven安装目录下/opt/maven/conf的conf/settings.xml文件内增加一段更改镜像地址为阿里云的maven,在mvn compile可解决此问题
nexus-aliyun
*
Nexus aliyun
http://maven.aliyun.com/nexus/content/groups/public
如果出现这个错误,就是找不到tool.jar直接复制一个tool.jar,源码包里面我复制的有。
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.3.2:compile (default-compile) on project message-thrift-service-api: Compilation failure
[ERROR] Unable to locate the Javac Compiler in:
[ERROR] /usr/lib/jvm/java-8-openjdk-amd64/jre/../lib/tools.jar
[ERROR] Please ensure you are using JDK 1.4 or above and
[ERROR] not a JRE (the com.sun.tools.javac.Main class is required).
[ERROR] In most cases you can change the location of your Java
[ERROR] installation by setting the JAVA_HOME environment variable.
复制命令
yum install lrzsz
#rz 选择tool.jar
docker cp tool.jar 容器ID:/usr/lib/jvm/java-8-openjdk-amd64/jre/../lib/tools.jar
···
![](https://upload-images.jianshu.io/upload_images/11223715-27d864caa1b8bffa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
>配置环境变量
```bash
docker exec -it 容器ID /bin/bash
vi ~/.bashrc
#配置下面的环境变量
set JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
export PATH=${JAVA_HOME}/bin:$PATH
- 在此尝试构建
#!groovy
pipeline {
//之前说过jenkins是支持集群的,但是咱们这里不需要集群的方式,因为有了k8s。any在任何可用的agent 上执行
agent any
//环境变量,
environment {
REPOSITORY="ssh://[email protected]:2222/liming/microservice.git"
MODULE="user-edge-service"
}
//流水线是如何提前,都是通过很多个stages下面的stage
stages {
stage('获取代码'){
steps{
echo " start fetch code from git ssh://192.168.66.101:2222/liming/microservice.git"
deleteDir()
git "${REPOSITORY}"
}
}
stage('编译+单元测试') {
steps{
echo " start compile"
sh "mvn -U -pl ${MODULE} -am clean package"
}
}
}
}
jenkins完成推送到官网镜像
- 创建build-imge文件
- docker login登录到docker仓库中(之前已经说过了,我的机器内存比较小,无法启动私有的docker仓库我通过的官网的,不管哪个都在jenkins的机器上登录下,push的时候就不报错了)
cd ~
vi build-image.sh
chmod 775 build-image.sh
docker login
#!groovy
pipeline {
//之前说过jenkins是支持集群的,但是咱们这里不需要集群的方式,因为有了k8s。any在任何可用的agent 上执行
agent any
//环境变量,
environment {
REPOSITORY="ssh://[email protected]:2222/liming/microservice.git"
MODULE="user-edge-service"
SCRIPT_PATH="/root/"
}
//流水线是如何提前,都是通过很多个stages下面的stage
stages {
stage('获取代码'){
steps{
echo " start fetch code from git ssh://192.168.66.101:2222/liming/microservice.git"
deleteDir()
git "${REPOSITORY}"
}
}
stage('代码静态检查') {
steps{
echo " start code check"
}
}
stage('编译+单元测试') {
steps{
echo " start compile"
sh "mvn -U -pl ${MODULE} -am clean package"
}
}
stage('构建镜像') {
steps{
echo " start build image"
sh "#{SCRIPT_PATH}/build-image.sh ${MODULE}"
}
}
stage('发布系统') {
steps{
echo " start deploy"
sh "#{SCRIPT_PATH}/deploy.sh ${MODULE} ${MODULE}"
}
}
}
}
build-image.sh
#!/bin/bash
MODULE=$1
TIME=`date "+%Y%m%d%H%M"`
GIT_REVISION=`git log -1 --pretty=format:"%h"`
IMAGE_NAME=zhugeaming/${MODULE}:${TIME}_${GIT_REVISION}
cd ${MODULE}
docker build -t ${GIT_REVISION} .
docker push ${GIT_REVISION}
deply.sh
#!/bin/bash
IMAGE=`cat IMAGE_NAME`
DEPLOYMENT=$1
MODULE=$2
echo "update image to ${IMAGE}"
kubectl set image deployment/${DEPLOYMENT} ${MODULE} =${MODULE}
PS:最后总结下,建议jenkins不要使用容器安装,我用容器安装入了至少十几个坑,对了解命令还是有好处的。我总结几点
- 不要容器化jenkins,直接在机器上安装就可以了。容器本身都是单个个体,你想想里面还要装jdk,mvn,docker。如果jenkins容器化,等于容器里面还要装docker是不是很蛋疼。
- 使用pipline写脚本其实很简单本身就是流水线,比较负责命令建议使用shell脚本的方式,这是也方便维护。
- gitlab里面,outbound requests 允许
- jenkins里面有几个重要的点跨域允许访问,允许用户注册
- 写的流程有点复杂,我是边练边写的,但是记录了我遇到的各种问题,希望能对有问题的老铁有帮助。
- 里面的sh 脚本可能比较适合我,特别是build-image那块,建议自行修改。
- 不要用容器安装jenkins了 这个坑太大了,gitlab还是容器安装爽。
- 最后在说一点,jenkins的pipline语法不复杂,参考我的写你可以可以完成自动化构建,push镜像,更新服务这块其实也没完全弄好,我准备在继续好好研究下k8s,感觉k8s水太深,下次出专辑深啃一把!
8-4 CICD实践(下)
从2018年9月11日开始写高级篇到今天11月21日,短短2个月高级的收获还是满满的。docker命令越来越熟悉,jenkins在中级篇里没有涉及到,这次也把jenkins的内容给补充了,在中级篇里k8s,一直安装很多坑,到现在可以顺利的安装k8s,而且一步一步安装了解了etc,apiserver,ControllerManager。但是感觉还是不够深入,下次计划继续k8s和实践。重点还是实践和k8s。
一起回顾下
####微服务的概念
1. 从软件架构的进化引入的微服务,了解微服务的概念,知道了微服务的架构并不是完美无缺的,有很多优势,独立性(有自己的独立栈),敏捷性(快速的迭代)。问题,数量庞大,指数级的增加我们必须考虑的它们之间如何进行通信,服务编排也是我们需要考虑的。
- 微服务带来的问题分析,服务间的通讯(一对一,一对多,同步异步),通信协议的维度分析了长见的协议(restapi,rpc,消息队列),也对比分析了几个rpc框架(thrift,dubbo,motan,grpc)。服务编排,首先使用了对比的方式,展示了传统服务和微服务在服务发现,服务更新,服务扩缩容上是什么样子,了解了微服务为什么不能像传统服务那样支持。然后引出了服务编排的概念。
3.springboot&springcloud的内容,毕竟他们都是线下流行的跟微服务密切相关的,犹豫跟微服务相关的很多,初学者很容易混乱,让老铁在大脑中对springboot和springcloud有个关键的认识,知道它的使命是什么,它的核心内容,以及它们在微服务中扮演什么角色。
微服务的开发
分析了微服务的架构,也分别开发了多个微服务,在开发的过程中,体验了springboot,springcloud,dubbo,跨语言的RPC通信thift,然后这只是刚刚开始,其实都是为了服务编排考虑的。
为服务编排做准备
- 服务docker化
- Docker compose
- Docker 仓库
>搭建了一个私有的仓库harbor
完事具备,只欠编排
学习本身是学类庞统的。学会一个其他基本都是一个原理。都是从入门开始,到架构设计,到环境搭建,最后到部署。
- Mesos(MySes可不是MeSes)
起步比较早很多大公司,还在用。
- Docker Swarm
已经在docker的安装里面了。 官方的原汁原味。
- kubernetes
犹豫kubernetes门槛比较高,我们花了前面几个框架几倍的时间来研究它,但是说实话,还没完全的搞懂,为了让大家更好的理解。部署了2次,一次非认证授权,一次需要认证。其实认证就是ssl 哈哈。一个模块一个模块纯手工的搭建了k8s集群。然后在集群上演示了官方的实例。
- CICD&Devops
最后体验了一把CICD和Devops,他们之间的关系还是很亲密的。服务编排为Devops落地提供了土壤。服务编排其实就是为CICD和Devops而生的。搭建了gitlab和jenkins的部署。
在jenkins下编写了pipline流水线脚本,以及在流水线里面编写的shell脚本。体验了CICD,从提交了代码完成打包,做镜像,push推送。最后完成服务的更新。最后说句千万别用jenkins的容器。这东西不适合用容器。
PS:从看到这里的基本都是真爱老铁了,在这里真心的谢谢大家。从docker初级,中级到高级。从4月份了解docker到现在已经7个月多了,对docker的执着学习一直在继续,中间也有老铁支出文章中的问题。感谢一路有你!真心的希望我写的文章能对各位老铁有帮助。文章前面放了一个沙滩脚印的图片,希望我们在一起学习docker的路上,你我一起让同行的人更多,感谢在这个过程中有老铁愿意分享我的文章,让更多的人一起在docker的路上。我们一直在赶路。一起在努力!你累吗,累就对了,因为至少证明你还活着!下一步学习docker脚步不会停止。继续开启我们的实践篇通过网上开源的项目,完成docker环境下的部署和运维。
第9章 课程总结
9-1 -课程总结