轻松构建微服务之docker和高效发布

微信公众号:内核小王子
关注可了解更多关于数据库,JVM内核相关的知识;
若是你有任何疑问也能够加我pigpdong[^1]java

docker

咱们先来了解下docker的原理,如何才能制造出一个真正隔离的软件运行环境.node

namespace

docker在建立容器进程的时候能够指定一组namespace参数,这样容器就只能看到当前namespace所限定的资源,文件,设备,网络。用户,配置信息,而对于宿主机和其余不相关的程序就看不到了,PID namespace让进程只看到当前namespace内的进程,Mount namespace让进程只看到当前namespace内的挂载点信息,Network namespace让进程只看到当前namespace内的网卡和配置信息,python

docker利用namespace机制隔离出一个软件执行环境,咱们能够用下图来将docker和虚拟机技术作一个对比mysql

image

一个centOS的KVM启动起来后,即便什么不作也须要消耗200M的内存,并且用户进程运行在虚拟机里,对宿主机操做系统的调用不可避免会受到虚拟化软件的拦截,而容器化的应用依然是宿主机上的一个普通进程.linux

cgroup

全名 linux control group,用来限制一个进程组可以使用的资源上限,如CPU,内存,网络等,另外Cgroup还可以对进程设置优先级和将进程挂起和恢复,cgroup对用户暴露的接口是一个文件系统,/sys/fs/cgroup下 这个目录下面有 cpuset,memery等文件,每个能够被管理的资源都会有一个文件,如何对一个进程设置资源访问上限呢?在/sys/fs/cgroup目录下新建一个文件夹,系统会默认建立上面一系列文件,而后docker容器启动后,将进程ID写入taskid文件中,在根据docker启动时候传人的参数修改对应的资源文件nginx

$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

Linux Cgroup的设计仍是比较易用的,简单理解就是一个子系统目录加上一组资源限制文件,而对docker等linux容器项目而言,只须要在每一个子系统下面,为每一个容器建立一个控制组也就是一个文件夹,让后在容器启动后写入进程PID.git

文件系统

即便开启了Mount Namespace,容器进程看到的文件系统仍是和宿主机同样的,mount namespace修改的时容器进程对挂载点的认知,他对容器进程视图的改变,必定伴随挂载操做才生效,可是做为一个普通用户,咱们但愿在容器内看到的文件系统就是一个独立的隔离环境,而不是继承自宿主机上的文件系统.算法

咱们能够在容器启动后立刻挂载他的整个根目录,这个挂载对宿主机不可见,而后经过chroot来更改change root file system更改进程的根目录到挂载的位置,通常会经过chroot挂载一个完整的linux的文件系统,可是不包括linux内核,这样当咱们交付一个docker镜像的时候不只包含须要运行的程序还包括这个程序依赖运行的这个环境,由于咱们打包了整个依赖的linux文件系统,对一个应用来讲,操做系统才是他所依赖的最完整的依赖库,sql

这个挂载在容器根目录下,用来为容器进程提供隔离后的执行环境的文件系统,就是所谓的容器镜像,它还有一个专业的名词rootFSdocker

因为rootfs里面打包的不只仅是用户程序,而是整个系统的文件目录,也就是是应用和应用依赖的类库和环境变量都在里面.

增量层

docker在镜像的设计中引入层的概念,也就是用户在制做docker镜像中的每一次修改都是在原来的rootfs上新增一层roofs,以后经过一种联合文件系统union fs的技术进行合并,合并的过程当中若是两个rootfs中有相同的文件则会用最外层的文件覆盖原来的文件来进行去重操做,举个例子,咱们从镜像中心pull一个mysql的镜像到本地,当咱们经过这个镜像建立一个容器的时候,就在这个镜像原有的层上新加了一个增roofs,这个文件系统只保留增量修改,包括文件的新增删除,修改,这个增量层会借助union fs和原有层一块儿挂载到同一个目录,这个增长的层能够读写,原有的其余层只能读,这样保证了全部对docker镜像的操做都是增量,以后用户能够commit这个镜像将对这个镜像的修改生成一个新的镜像,新的镜像就包含了原有的层和新增的层,只有最原始的层才是一个完整的linux fs, 那么既然只读层不容许修改,那么我怎么删除只读层的文件呢,这个时候只须要在读写层也就是最外层生成一个whiteout文件来遮挡原来的文件就能够了,

image

$ docker image inspect ubuntu:latest
...
     "RootFS": {
      "Type": "layers",
      "Layers": [
        "sha256:f49017d4d5ce9c0f544c...",
        "sha256:8f2b771487e9d6354080...",
        "sha256:ccd4d61916aaa2159429...",
        "sha256:c01d74f99de40e097c73...",
        "sha256:268a067217b5fe78e000..."
      ]
    }

dockerfile

能够经过docfile生成一个镜像,docfile里面能够指定 from的原始镜像,以及自定义操做例如拷贝文件等,容器启动命令等

例以下面的dockerfile,FROM原语指定了原始镜像是python:2.7-slim,避免了先从一个原始的ubantu镜像,而后经过apt-get install按照python

WORDIR 切换工做目录

ADD拷贝文件

RUN 在容器里执行shell

CMD 指定容器进程,至关于 docker rum python app.py

# 使用官方提供的 Python 开发镜像做为基础镜像
FROM python:2.7-slim

# 将工做目录切换为 /app
WORKDIR /app

# 将当前目录下的全部内容复制到 /app 下
ADD . /app

# 使用 pip 命令安装这个应用所须要的依赖
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# 容许外界访问容器的 80 端口
EXPOSE 80

# 设置环境变量
ENV NAME World

# 设置容器进程为:python app.py,即:这个 Python 应用的启动命令
CMD ["python", "app.py"]

容器网络

从以前的章节中咱们能够看到,容器只是一个被隔离的进程,这个进程只能看到他本身network namespace下的网络栈,所谓的网络栈包括了
网卡(network interface),回环设备(loopback device),路由表(route table),iptables规则,这些已经能够构成这个 进程和外界进行网络通信的基本环境.

容器做为宿主机内的一个进程,在不设置network namespace的状况下,可使用宿主机的网络栈.

$ docker run –d –net=host --name nginx-host nginx

以上例子,就是容器采用宿主机的网络,当容器启动后监听的是宿主机上的80端口

采用宿主机网络能够为容器提供最好的网络性能,可是咱们但愿容器有一个彻底被隔离的环境,包括网络环境,她应该有本身的IP地址和端口,这样就不会和其余进程有端口冲突,例如我运行4个ngnx容器进程,我但愿他们是彻底同样的,向运行在4台宿主机里面同样,都监听80端口.因此咱们但愿容器进程运行在本身的network namespace内.

咱们如今把容器当作,一台主机,两台主机之间想要通讯,就须要拉一条网线,多个主机之间想要通讯,就须要一个交换机用网线把他们连起来,在linux上,可以启到虚拟交换做用的设备就是网桥(Bridge),他是一个工做在数据链路层的设备,咱们能够把它当成一个二层交换机,而二层设备主要靠学习MAC地址对应的端口,并将数据包转发到对应的端口上去.

docker在安装的时候会在宿主机上建立一个叫docker0的网桥,而容器可用经过Veth Pair的虚拟设备,链接到这个网桥,Veth Pair这种设备在建立的时候会有两张虚拟网卡,从一个网卡里面发出的数据包会到达另一个网卡里,而且这两个网卡能够跨network namespace,这样Veth Pair能够理解为链接不一样namespace的网线.

下图是我启动了两个nginx容器,分别为nginx-1 ,nginx-2,咱们用ifconfig查看宿主机上的网卡信息

$ docker run –d --name nginx-1 nginx
$ docker run –d --name nginx-2 nginx

image

被插在网桥上的虚拟网卡,不会调用网络协议中栈处理数据包,只会像一个端口同样,将数据包交给网桥,由网桥进行转发.

下面咱们分析下一个宿主机内的两个容器之间的网络怎么通信?

当咱们在容器nginx-1内去ping一下nginx-2的ip,默认下是通的

image

  • 1.宿主机内的两个主机经过二层网络相通,nginx-1会先发一个ARP包来获取nginx-2的MAC地址
  • 2.nginx-1只能看到他本身network namespace内的网卡 nginx-1-eth-0 ,因此数据包从这个网卡发出,可是这个网卡是一个Veth Pair设备,这个设备的另一端在宿主机上默认的namespace内,而且是插在网桥docker-0上
  • 3.因为Veth Pair设备的做用,宿主机的虚拟网卡收到数据包后,直接交给网桥docker-0进行转发,而docker-0会把数据包广播给插在这个网桥上的其余虚拟网卡
  • 4.这样数据就会被广播到宿主机上的另一个虚拟网卡上了,这个虚拟网卡也是一个Veth Pair,他的另一端是nginx-2容器的namespace内的虚拟网卡,这个网卡将本身MAC地址回复
  • 5.这样nginx-1的namespace内的虚拟网卡就获取到了nginx-2的namespace内的网卡的MAC地址了,就能够组装数据包将请求发给nginx-2了
  • 6.一样数据包先根据Veth Pair设备到达宿主机namespace内的网卡,而后交给docker-0进行转发,因为此时docker-0网桥已经学习到了nginx-2的mac地址对应的端口了,只须要查CAM表查一下记录,转发到另一块宿主机的虚拟网卡,而后到达nginx-2的namespace内的网卡

以上就是同一个宿主机内的不一样docker容器经过Veth Pair设备和docker-0网桥通讯的流程,与此相似,容器和其余宿主机进行通讯,docker-0网桥在转发的时候会根据宿主机的路由规则,将数据转发给宿主机上的eth-0网卡,而后在由宿主机上德etho-进行转发.

那么容器怎么和其余宿主机内的网络通讯呢?

在docker默认配置下,一台宿主机内的docker-0网桥和另一个宿主机内的docker-0网桥没有任何关联,它们之间没办法相互关联,因此连在不一样网桥上的容器没有办法进行连通.咱们能够经过软件的方式,建立一整个集群共用的网桥,集群内全部的容器都连到这个网桥上,这个就是覆盖网络(overlay),为了实现这个网桥,咱们须要了解FLANNEL技术,以及他的量种实现:UDP,VXLAN

UDP的模式是在宿主机内增长一个软件进程,经过TUN设备,将数据包发给上层应用,而后在由应用层判断如何进行转发,目前UDP模式已经被淘汰,主要由于性能过低,涉及太屡次数据包从内核态到用户态的转发.

image

VXLAN是linux内核自己就支持的一种网络虚拟化技术,VXLAN维护一个虚拟的局域网,使在这个LAN之内的容器能够相互通讯.

image

从上图能够看出,为了能让二层网络可以打通,VXLAN须要在宿主机上设置一个特殊的设备做为隧道的两端,这个设备就是VTEP (Vxlan Tunnel Endpoin ) 虚拟隧道端点.而VTEP的做用和上面UDP的应用程序相似,他会拆包和封包,只不过他在数据链路层而不是涉及用户太和内核态的转换.

kubernetes

k8s是一个作容器编排和调度的工具,kubernets的最小调度单元是POD,一个POD能够管理一组同生命周期的容器,k8s提供一个restful的客户端api供用户使用,因此会有一个APIserver来接受请求,经过etcd做为数据库来存储请求中得CRUD操做,而其余模块例如控制器中的调度单元,会扫描数据库中的记录,若是有新的POD尚未分配物理节点,则会执行调度动做,若是发现新增了副本数量,就会增长POD副本,若是修改了POD相关配置就去执行,而每个节点上面都会容许一个kube-proxy用来接收外部请求后转发,而docker采用的插拔容器,可使用docker引擎,也能够用其余的引擎。

image

下面咱们分析下k8s下相关的概念

  • 1.POD POD是kubernate的最小可操做单元,若是咱们把容器当作一个特殊的进程,那POD就是一个有相同生命周期的进程组,POD里的全部容器共享一个Network namespace,而且能够共享一个Volume,那么kubernate怎么实现呢? k8s会在容器建立的时候先建立一个中间容器infra,而后其余的容器和这个容器共享namespace来实现.这样一个POD内的多个容器能够经过localhost进行通讯,一个POD只有一个IP地址,POD的生命周期和infra相关,和容器A和B无关.

  • 2.MASTER Kubernatis集群中的master节点,能够部署在物理机或者虚拟机上,master节点负责维护集群的状态,以及对外提供API服务,master节点上部署如下3种组件

  • 2.1 API SERVER,做为kubernatis系统的入口,封装了核心对象的增删改查操做以及配置操做,以RESTFUL api的方式以及命令行的方式kubectl将接口暴露给外部客户和内部组件使用,维护的对象会被持久化到etcd中.

  • 2.2 Schedule 调度器:为新建的POD选择NODE,也就是分配机器,这个调度器也是能够插拔的,能够换成其余调度器,调度器能够考虑根据NODE的负载以及物理位置等参数设计调度算法.

  • 2.3 Controller 控制器:全部资源的自动化控制中心,是一个大总管,例如Node Controller 用于管理NODE对象,接收NODE节点的使用状况,和NODE生命周期的管理即:建立和销毁,以及心跳检查等,Replication Controller ,副本控制器,监测业务集群中POD数量是否符合预期,若是不够会建立,多余会销毁,当管理员修改了预期的值后,该进程就会进行响应.

  • 3.NODE 节点是kubernatis集群中,相对于master而言的工做主机,这些主机能够说物理机,也能够是虚拟机,POD容器都运行在这些工做节点上,每一个Node上都运行着一个叫kublet的进程,用来启动和管理POD,NODE被master管理,一个NODE宕机后,master会将这台node上部署的POD在其余NODE上从新建立并部署起来,NODE节点上部署有如下组件.

  • 3.1 kubelet,做为一个单独进程运行在NODE节点上,主要功能是:容器的管理,镜像的管理,数据卷的管理,同时kubelet也提供restful接口服务,当master节点的控制器能够下发请求到node上的kubelet进程,进行某个POD的建立,启动容器和销毁,并监听容器运行状态汇报给master.

  • 3.2 kube-proxy,负责为POD建立代理对象,用来实现访问POD提供服务的网络请求的路由和转发,该proxy能够提供必定的负载均衡和SDN的功能

  • 3.3 容器环境,能够是docker也能够是其余容器技术

  • 4.etcd做为一个键值数据库,存储集群的元数据,以及POD的配置信息,并具备消息订阅功能,因此,当用户修改了POD预期副本数量,Replication Controller就能够感知到, etcs采用raft协议来保证集群环境下的数据一致性,并提供restful的api来供客户端使用,kubernatis中的网络配置信息,以及操做记录和各个对象的状态都存储在etcd中,多个组件间的交互都须要用到etcd,因此etcs对整个k8s集群特别重要,因此etcd必须保证高可用.

image

Devops

Devops用来保证,开发,运维,测试之间的高效沟通和协做,是软件发布更加简单和便捷.咱们能够简单的将Devops思想抽象成两个产品,一个产品用来作项目管理,一个产品用来作软件发布和集成.

咱们能够尝试简单思考下一个项目管理的系统须要提供哪些功能:

  • 1.支持看板视图,让团队能够快速作信息同步,面板信息应该包括:项目描述,项目进度以及碰到的问题,而项目进度能够根据时间列出:需求立项,需求评审,设计评审,技术方案评审,开发,测试,发布.

  • 2.项目迭代管理,提供各类维度报表,用来分析和预估后期项目周期.

  • 3.代码关联,能够将需求在gitlab上关联代码

而一个软件发布的系统应该提供如下功能

  • 1.代码管理,代码提交能够关联对应的需求,支持代码在线review

  • 2.持续集成,支持每日定时,或者代码提交自动触发构建动做

  • 3.支持从gitlab上拉取最新的代码,而后在特定环境下打包,而后根据dockerfile生成镜像并提交

  • 4.支持镜像管理

  • 5.支持自动化单元测试,接口测试,性能测试

  • 6.持续部署,支持根据生成的镜像自动部署测试环境和开发环境,使开发环境能够随时访问

  • 7.灰度发布,能够灰度发布到生产环境和预发布环境

咱们如何利用k8s作到自动化打包和发布?

  • 1.建立pileline 指定项目名称和对应的tag,以及依赖工程,一个pipeline指一个完整的项目生命周期(开发提交代码到代码仓库,打包,部署到开发环境,自动化测试,部署到测试环境,部署到生产环境)
  • 2.根据项目名称和tag去gitlab上拉取最新的代码(利用java里的Runtime执行shell脚本)
  • 3.利用maven进行打包,这个时候能够为maven建立一个单独的workspace(shell脚本)
  • 4.根据预先写好的docfile,拷贝maven打的包生成镜像,并上传镜像 (shell脚本)
  • 5.经过k8s的api在测试环境发布升级
  • 6.经过灰度等方案发布到生产环境

蓝绿发布

通常在反向代理层,例如nginx,的配置文件作一个软链接,分别指向两套环境,咱们将这两套环境分别称为生产环境和预发布环境,正常状况下配置文件的软链接指向生成环境,这个时候流量都会进入到生成环境的集群,发布的时候先发布预发布环境的机器,因为没有流量进入,因此机器重启没有太大影响,机器发布完成后,能够经过其余入口,例如内部修改host或者其余流量入口进行内部测试,若是内部测试经过,就能够修改软链接指向预发布环境的集群,而后在进行验证当外部流量进来后,是否正常,若是不正常能够直接在把流量切回去,若是正常就能够升级生成环境的机器,而后在把流量切到生产环境

灰度发布

灰度发布能够在发布前,将一部分比例的机器的流量切走,而后进行软件升级,升级完成后把流量切回来,而后进行验证,验证有问题在把流量掐掉,没有问题能够慢慢按比例对外,这种方式须要新老版本的服务同时对外提供服务,有些状况下咱们把经过白名单或者固定用户的形式开发服务也叫灰度.

相关文章
相关标签/搜索