Docker

Docker

关于swarm网络不能访问的问题

回复

ighack 发起了问题 • 1 人关注 • 0 个回复 • 8 次浏览 • 2019-06-19 11:57 • 来自相关话题

docker挂载mysql5.7日志无效

styshoo 回复了问题 • 2 人关注 • 1 个回复 • 99 次浏览 • 2019-06-19 08:22 • 来自相关话题

在Docker上编译OpenJDK 8

李颖杰 发表了文章 • 0 个评论 • 136 次浏览 • 2019-06-18 11:06 • 来自相关话题

以前曾经试过在VMware上安装Linux,但是每次都不顺利,例如Linux环境,预装依赖软件,OpenJDK源码的选择等环境都会遇到问题,一旦失败再重新开始挺费时间的,现在用Docker就省事多了,镜像和容器的创建和删除都比较简单干净,专心做好Dock ...查看全部

以前曾经试过在VMware上安装Linux,但是每次都不顺利,例如Linux环境,预装依赖软件,OpenJDK源码的选择等环境都会遇到问题,一旦失败再重新开始挺费时间的,现在用Docker就省事多了,镜像和容器的创建和删除都比较简单干净,专心做好Dockerfile就行,下面我们一起来实践一下吧。



本次实战用到的所有文件,已经打包到GitHub上,路径是git@github.com:zq2599/centos7_build_openjdk8.git,欢迎您来使用(git clone git@github.com:zq2599/centos7_build_openjdk8.git)。



本次编译实战的基本步骤如下:


编写的Dockerfile中要做的如下的事情:




  1. 安装依赖的软件;

  2. 把OpenJDK的源码复制到镜像中;



在编写Dockerfile之前要做三个重要的选择,如下:




  1. Linux:我选择了CentOS 7,之前试过Ubuntu 16.04,但是在Configure的时候提示”freetype”没有安装,我按照提示去装了,再次Configure的时候继续提示”freetype”没有安装……(此问题现在还没解决,如果您解决过相同问题,请您告诉一下解决方法,谢谢了!)

  2. OpenJDK源码,这次要编译的是OpenJDK 8,源码的下载地址在这里

  3. Bootstrap JDK:即编译时要用到的JDK,下载了OpenJDK的源码后,解压开可以看到“README-builds.html”这个文件,里面有对Bootstrap JDK的描述:




看得出,需要安装JDK 7来编译OpenJDK 8的源码。



OK,关键问题都已确认,即将开始Dockerfile制作,不过制作之前还有个小问题需要先想好:本次我打算把制作镜像所需的Dockerfile和依赖文件都放到GitHub上去,这样做的好处有两个:




  1. 读者们从Git上clone下来之后直接执行Docker build就能在本地构建镜像;

  2. daocloud.io网站上支持通过执行GitHub目录的方式在线构建镜像,后面我们会实践在daocloud.io上构建镜像并部署到腾讯云或者阿里云服务器上。



上传到Gith=Hub时,除了Dockerfile,还要上传的文件有两个:JDK 1.7和OpenJDK 8源码,都超过了100M,如下图:


这就麻烦了,GitHub上传文件的时候,单个文件不能超过100M,否则push的时候会被服务器拒绝,解决这个问题有两个办法:



1、构建镜像的时候不要把这两个文件复制到镜像中了,改为在Dockerfile中通过执行wget命令将这两个文件分别下载到镜像中,OpenJDK的下载路径是:http://www.java.net/download/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip,而OpenJDK 1.7的下载路径就难办了,Oracle上下载历史版本的时候,是要做登录操作的,这个在Dockerfile中难以实现,找到了一个下载地址:https://mirror.its.sfu.ca/mirror/CentOS-Third-Party/NSG/common/x86_64/jdk-7u80-linux-x64.rpm,但是下载速度很慢,最少一个小时以上了,所以wget这种方法看似简单,但下载文件耗时实在太长;



2、第二种方法比较简单易用,就是在Mac或者Linux上先用split命令将文件分割成多个,再上传到GitHub上,在Dockerfile中有对应的命令将分割后的文件恢复成分割前的原文件,具体的分割命令如下:





split -b 50m jdk-7u71-linux-x64.rpm jdkrpm-


这个命令是将jdk-7u71-linux-x64.rpm分割成不超过50m的多个文件,分割后的文件以jdkrpm-作为文件名的前缀,如下图:


OpenJDK的源码用如下命令分割:





split -b 50m openjdk-8-src-b132-03_mar_2014.zip openjdksrc-


Dockerfile中,从分割文件恢复以上两个原文件的命令为:





cat jdkrpm-* > jdk-7u71-linux-x64.rpm
cat openjdksrc-* > openjdk-8-src-b132-03_mar_2014.zip


以上就是两种处理大文件的方法,本文用的是第二种,即先分割上传到Git,在Dockerfile中将已分割文件恢复成原文件再使用。



好了,前期的准备工作已经做完了,现在可以编写Dockerfile文件了,整个文件中要做的事情列出如下:



1、安装依赖软件,例如libXtst-devel,libXt-devel等等,这些都是编译前的Configure命令要检查的,检查不过无法进行编译;



2、把分割后的OpenJDK源码复制到镜像文件中,再合成,再解压;



3、安装JDK 7,把分割后的安装文件复制到镜像中合成,然后安装;



4、为了方便用户进入容器后快速开始编译,我们做了一个shell脚本start_make.sh,把这个脚本也要复制到镜像中,内容如下:





#!/bin/bash
$WORK_PATH/$OPENJDK_SRC_DIR/configure
echo "start make"
cd $WORK_PATH/$OPENJDK_SRC_DIR
make all ZIP_DEBUGINFO_FILES=0 DISABLE_HOTSPOT_OS_VERSION_CHECK=OK


5、清理无用的文件,例如OpenJDK源码的压缩文件,JDK 7的安装文件等;



按照以上步骤,最终写出的Dockerfile文件如下:





# Docker image of compile and build enviroment for openjdk8
# VERSION 0.0.1
# Author: bolingcavalry

#基础镜像使用CentOS 7
FROM centos:centos7

#作者
MAINTAINER BolingCavalry

#定义工作目录
ENV WORK_PATH /usr/local

#定义JDK 1.7的文件名
ENV JDK_RPM_FILE jdk-7u71-linux-x64.rpm

#定义OpenJDK源码的文件名
ENV OPENJDK_SRC_ZIP openjdk-8-src-b132-03_mar_2014.zip

#定义解压缩后的文件名
ENV OPENJDK_SRC_DIR openjdk

#yum更新
RUN yum -y update

#安装工具集
RUN yum -y groupinstall "Development Tools"

#安装即将用到的软件
RUN yum -y install unzip libXtst-devel libXt-devel libXrender-devel cups-devel freetype-devel alsa-lib-devel which

#把分割过的JDK 1.7安装文件复制到工作目录
COPY ./jdkrpm-* $WORK_PATH/

#用本地分割过的文件恢复原有的JDK 1.7的安装文件
RUN cat $WORK_PATH/jdkrpm-* > $WORK_PATH/$JDK_RPM_FILE

#本地安装JDK 1.7
RUN yum -y localinstall $WORK_PATH/$JDK_RPM_FILE

#把分割过的OpenJDK 8的源码压缩包复制到工作目录
COPY ./openjdksrc-* $WORK_PATH/

#用本地分割过的文件恢复原有的OpenJDK 8的源码压缩包
RUN cat $WORK_PATH/openjdksrc-* > $WORK_PATH/$OPENJDK_SRC_ZIP

#解压缩源码
RUN unzip $WORK_PATH/$OPENJDK_SRC_ZIP -d $WORK_PATH

#复制启动编译的shell
COPY ./start_make.sh $WORK_PATH/$OPENJDK_SRC_DIR/

#给执行文件增加可执行权限:Configure文件
RUN chmod a+x $WORK_PATH/$OPENJDK_SRC_DIR/configure

#给执行文件增加可执行权限:启动编译文件
RUN chmod a+x $WORK_PATH/$OPENJDK_SRC_DIR/start_make.sh

#删除分割文件
RUN rm $WORK_PATH/jdkrpm-*

#删除分割文件
RUN rm $WORK_PATH/openjdksrc-*

#删除JDK安装包文件
RUN rm $WORK_PATH/$JDK_RPM_FILE

#删除OpenJDK源码压缩文件
RUN rm $WORK_PATH/$OPENJDK_SRC_ZIP


至此,镜像文件制作所需的材料都已经齐全了,如下图:


现在让我们开始制作镜像吧,打开终端,进入Dockerfile所在目录,执行命令:





docker build -t bolingcavalryopenjdk:0.0.1 .


因为要在线安装不少的软件,所以可能耗时会略长,和网络带宽有关,我在住处用家庭网络大概10分钟左右构建成功,执行目录docker images查看镜像,新的镜像文件已经生成了,如下图:


现在启动一个容器试试吧:





docker run --name=jdk001 -idt bolingcavalryopenjdk:0.0.1


容器已经启动,再执行以下命令进入容器:





docker exec -it jdk001 /bin/bash


进去后,直接到/usr/local/openjdk目录下,执行./start_make.sh,开始编译了,整个过程的耗时和当前电脑的硬件配置有关,我用i7处理器的Mac Pro 15大约要用20多分钟,编译结束后会有类似下图的输出:


这时候去/usr/local/openjdk目录下看看,发现多了一个build目录,这里面就是编译好的结果,如下图:


build目录下只有一个linux-x86_64-normal-server-release目录,再进去就能看到JDK目录了,如下图:


进入/usr/local/openjdk/build/linux-x86_64-normal-server-release/jdk/bin目录,会发现里面有Java文件,执行./java -version输出如下:


新的JDK信息已经打印出来了,OpenJDK Runtime Enviroment信息已经说明了这是个最新构建的JDK环境。



至此我们的本次实战就结束了,面对如此干净的编译环境和现成的源码,读者您是否有一种改动一番源码的冲动,然后构建一个个性化的属于自己的JDK,建议您自己动手修改和编译OpenJDK源码,以加深对JVM的认识。

国内镜像源解决方式

徐新坤 发表了文章 • 0 个评论 • 207 次浏览 • 2019-06-17 09:25 • 来自相关话题

Azure China提供了目前用过的质量最好的镜像源,涵盖了docker.io,gcr.io,quay.io。无论是速度还是覆盖范围,体验都极佳。而且都支持匿名拉取,也就是不需要登录。这点特别友好。azk8s.cn支持的镜像代理转换如下列表。 ...查看全部

Azure China提供了目前用过的质量最好的镜像源,涵盖了docker.io,gcr.io,quay.io。无论是速度还是覆盖范围,体验都极佳。而且都支持匿名拉取,也就是不需要登录。这点特别友好。azk8s.cn支持的镜像代理转换如下列表。






globalproxy in Chinaformatexample
dockerhub (docker.io)dockerhub.azk8s.cndockerhub.azk8s.cn//:dockerhub.azk8s.cn/microsoft/azure-cli:2.0.61 dockerhub.azk8s.cn/library/nginx:1.15
gcr.iogcr.azk8s.cngcr.azk8s.cn//:gcr.azk8s.cn/google_containers/hyperkube-amd64:v1.13.5
quay.ioquay.azk8s.cnquay.azk8s.cn//:quay.azk8s.cn/deis/go-dev:v1.10.0



Note:
k8s.gcr.io would redirect to gcr.io/google-containers, following image urls are identical:



k8s.gcr.io/pause-amd64:3.1
gcr.io/google_containers/pause-amd64:3.1



这里,我开发了一个小的脚本azk8spull,这个脚本可以自动根据镜像名称进行解析,转换为azure的mirror镜像源域名。并进行拉取。拉取完成后会自动进行tag重命名为原本的镜像名。该脚本已经开源在 https://github.com/xuxinkun/littleTools#azk8spull 上。以下是使用样例。


[root@node-64-216 ~]# azk8spull quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1
## azk8spull try to pull image from mirror quay.azk8s.cn/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1.
0.24.1: Pulling from kubernetes-ingress-controller/nginx-ingress-controller
Digest: sha256:76861d167e4e3db18f2672fd3435396aaa898ddf4d1128375d7c93b91c59f87f
Status: Image is up to date for quay.azk8s.cn/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1
## azk8spull try to tag quay.azk8s.cn/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1 to quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1.
## azk8spull finish pulling.

[root@node-64-216 ~]# azk8spull k8s.gcr.io/pause-amd64:3.1
## azk8spull try to pull image from mirror gcr.azk8s.cn/google_containers/pause-amd64:3.1.
3.1: Pulling from google_containers/pause-amd64
Digest: sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610
Status: Image is up to date for gcr.azk8s.cn/google_containers/pause-amd64:3.1
## azk8spull try to tag gcr.azk8s.cn/google_containers/pause-amd64:3.1 to k8s.gcr.io/pause-amd64:3.1.
## azk8spull finish pulling.



参考资料: 


- Docker Registry Proxy Cache 帮助 http://mirror.azure.cn/help/docker-registry-proxy-cache.html 

- Azure China container registry proxy https://github.com/Azure/container-service-for-azure-china/tree/master/aks#22-container-registry-proxy


原文链接:docker/kubernetes国内源/镜像源解决方式

使用Docker高效搭建开发环境

齐达内 发表了文章 • 0 个评论 • 497 次浏览 • 2019-06-04 23:00 • 来自相关话题

【编者的话】Docker作为轻量级的基于容器的解决方案,它对系统侵入性低,容易移植,天生就适合做复杂业务部署和开发环境搭建,今天给大家带来的是奇虎360开发是如何使用Docker高效搭建开发环境的。 作为一个平时喜 ...查看全部

【编者的话】Docker作为轻量级的基于容器的解决方案,它对系统侵入性低,容易移植,天生就适合做复杂业务部署和开发环境搭建,今天给大家带来的是奇虎360开发是如何使用Docker高效搭建开发环境的。



作为一个平时喜欢折腾的开发人员,我喜欢尝试各种环境,使用感兴趣的各种开源软件。



同时,我也是有一些相对的小洁癖,很喜欢Linux中权限最小化原则,我也不喜欢自己的环境中有太多不知道的东西。



做了多年的Web开发,我接触到的环境大致如下:




  1. 操作系统从CentOS 5到CentOS 7;

  2. Web Server从Apache到Nginx;

  3. 开发语言从最初的PHP 5.2到PHP 7,又到现在主要使用Go,马上还会开始接触C++;

  4. 数据库从MySQL 5.1到现在的5.7,前阵子又开始折腾MariaDB;

  5. Cache选型从Memcache到Redis;

  6. 队列用过Kafka,去年开始大量使用NSQ;



公司虽然有专门负责部署、运维这些服务的同学,但我在开发的时候,还是喜欢自己来搭建这些东西,因为这样通常可以对使用到的服务有更多的认识,也能帮助自己使用的更好。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态



今天我就来和大家分享下我是如何高效的搭建好自己的开发环境的。





搭建前说明



这里先说明一点,对每个开源软件,我几乎都是自己编译部署的,而不会使用类似yum install这种方式,也很少直接下载官方编译好的二进制包,这都是为了能多深入了解用到的开源软件。



但一些依赖的动态库文件,如zlib等,还有编译工具,如GCC、make等,我都是通过方便的yum install这种方式直接安装的,否则会累死。





传统做法



我在很长的一段时间内,都是把每个软件的编译、安装过程写成一个脚本,之后再需要用的时候直接运行脚本即可,但这样的方式,通常会遇到下面这些问题:




  1. 脚本只能在我当时的操作系统环境下运行。记得当时购买过不同服务商的VPS,虽然不同VPS我都使用同样的Linux发行版,但脚本通常都不能一键跑完。这也是没办法,因为每个VPS服务商都会制作自己的操作系统镜像版本。

  2. 操作系统升级,如CentOS 5 - 6,或是换为Ubuntu,这样基本上脚本都跑不了。

  3. 软件升级,如MySQL 5.2 - 5.6,构建工具改为CMake,依赖库改变或升级。

  4. 如果某个软件依赖的公共库版本和其它软件不同,且公共库升级后和旧版不兼容,那你就只能为这个软件单独编译公共库了,如果只是普通的公共库还好,但如果是所需要的编译工具版本不同,那可就惨了。



上面这些问题,如果你想每个发行版维护一个脚本,那会累死,因为一旦你每次想升级一个软件,难道每个发行版都要编译一遍吗?这就变成了收获价值很低的体力劳动了。



由于喜欢折腾的个性,我对操作系统的升级以及软件包版本的升级又经常发生,所以一直以来,我都在寻找一个好方法,能很方便的维护好自己的开发环境,尽量做到只=新东西只为它工作一次,最后我找到了Docker,目前我都是用它来搭建自己的开发环境的。





Docker做法



先概括介绍下我的方法:




  1. 让每个软件运行在容器中,因为运行的容器环境是可以固定下来的,所以编译安装脚本写一个就可以了。

  2. 代码使用数据卷的方式加载到需要的容器中。

  3. 因为是开发环境,所以网络方面使用最简单的–net=host。

  4. 将镜像的创建、容器的启动维护在Git项目中,并抽象出统一的构建过程,很方面的做到新软件接入,新机器部署。



下面用实例来说明把:





示例Nginx环境构建



我将构建过程放到Git中:https://gitee.com/andals/docker-nginx



Readme中记录了构建所需要执行的脚本命令,大家访问上面的网址就可以看到,这里我简单介绍下项目的结构:





├── Dockerfile        //创建镜像的Dockerfile
├── pkg //编译好的二进制包,可以直接使用,此外软件运行的一些配置文件或第三方包也放在这里
│ ├── conf
│ │ ├── fastcgi.conf
│ │ ├── http.d
│ │ ├── include
│ │ ├── koi-utf
│ │ ├── koi-win
│ │ ├── logrotate.conf
│ │ ├── logrotate.d
│ │ ├── mime.types
│ │ ├── Nginx.conf
│ │ ├── scgi_params
│ │ ├── uwsgi_params
│ │ └── win-utf
│ ├── luajit-2.0.3.tar.gz
│ └── Nginx-1.8.1.tar.gz
├── README.md
├── script //里面放构建脚本
│ ├── build_image.sh //构建镜像使用
│ ├── build_pkg.sh //编译软件包时使用
│ ├── init.sh //容器启动时执行
│ └── pre_build.sh //软件依赖的共享库,编译和构建时都会用到
└── src //编译时需要的软件源码
├── modules
│ ├── ngx_devel_kit-0.2.19.tgz
│ ├── ngx_echo-0.53.tgz
│ └── ngx_lua-0.9.7.tgz
├── Nginx-1.8.1.tar.gz
└── openssl-1.0.2h.tar.gz




DockerFile说明



Dockerfile结构如下:





FROM andals/CentOS:7
MAINTAINER ligang

LABEL name="Nginx Image"
LABEL vendor="Andals"

COPY pkg/ /build/pkg/
COPY script/ /build/script/

RUN /build/script/build_image.sh

CMD /build/script/init.sh


整个构建框架为:




  1. 把构建需要的包(PKG目录中)放到镜像中

  2. 把构建脚本放到镜像中

  3. 执行构建脚本

  4. 容器启动时,执行init.sh,里面启动相应的服务



Readme.md中记录了执行构建的命令和容器运行命令,示例运行如下:





ligang@vm-xUbuntu16 ~/devspace/dbuild $ git clone git@gitee.com:andals/docker-Nginx.git Nginx
Cloning into 'Nginx'...
......

ligang@vm-xUbuntu16 ~/devspace/dbuild $ cd Nginx/
ligang@vm-xUbuntu16 ~/devspace/dbuild/Nginx $ ngxVer=1.8.1
ligang@vm-xUbuntu16 ~/devspace/dbuild/Nginx $ docker build -t andals/Nginx:${ngxVer} ./
Sending build context to Docker daemon 30.7MB
Step 1/8 : FROM andals/CentOS:7
......
Successfully built ea8147743031
Successfully tagged andals/Nginx:1.8.1

ligang@vm-xUbuntu16 ~/devspace/dbuild/Nginx $ docker run -d --name=Nginx-${ngxVer} --volumes-from=data-home -v /data/Nginx:/data/Nginx --net=host andals/Nginx:${ngxVer}
dbf3c0617eb34c4b1b4ea54c2961989612d5474db3b1acd1d717221e6e5cb516


说明: –volumes-from=data-home这个就是我放置代码的数据卷,我喜欢把代码放到$HOME下面,所以这个卷的相关信息如下:





ligang@vm-xUbuntu16 ~ $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
578912a08ea7 andals/CentOS:7 "echo Data Volumn Ho…" 9 days ago Exited (0) 9 days ago data-home
......

ligang@vm-xUbuntu16 ~ $ docker inspect 578912a08ea7
......
"Mounts": [
{
"Type": "bind",
"Source": "/home",
"Destination": "/home",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
......


/data/Nginx中放置Nginx的conf、log等,每个软件运行时的conf、log、data等我都统一放置在/data下面,如:





ligang@vm-xUbuntu16 ~ $ tree -d /data/ -L 1
/data/
├── mariadb
├── Nginx
└── redis

ligang@vm-xUbuntu16 ~ $ tree -d /data/Nginx/
/data/Nginx/
├── conf
│ ├── http.d
│ ├── include
│ └── logrotate.d
└── logs


启动容器时使用–net=host,作为开发环境简单实用 我就是通过这种方法完成了开发环境的构建,不再有多余的重复工作,并且新机器部署开发环境效率极高。



我目前用到的容器环境如下:





ligang@vm-xUbuntu16 ~ $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dbf3c0617eb3 andals/Nginx:1.8.1 "/bin/sh -c /build/s…" 33 minutes ago Up 33 minutes Nginx-1.8.1
3e31ef433298 andals/php:7.1.9 "/bin/sh -c /build/s…" 8 hours ago Up 8 hours php-7.1.9
360f94bf9c43 andals/jekyll:latest "/bin/sh -c /build/s…" 9 days ago Up 10 hours jekyll-latest
0a7d58d1ca5e andals/redis:4.0.8 "/bin/sh -c /build/s…" 9 days ago Up 10 hours redis-4.0.8
fdaa655b4a11 andals/samba:4.4.16 "/bin/sh -c /build/s…" 9 days ago Up 10 hours samba-4.4.16
6ad00a69befd andals/mariadb:10.2.14 "/bin/sh -c /build/s…" 9 days ago Up 10 hours mariadb-10.2.14
578912a08ea7 andals/CentOS:7 "echo Data Volumn Ho…" 9 days ago Exited (0) 9 days ago data-home




辅助工具dockerbox



使用Docker环境后,有个问题,就是没有办法很方便的和软件交互。 这是因为软件都执行在容器中,比如重启Nginx吧,需要下面这几步:




  1. 找到Nginx这个容器

  2. 进入Nginx这个容器

  3. 在容器里面再执行reload



也可以是:




  1. 找到Nginx这个容器

  2. 使用docker exec



但无论哪种方式,都比原先直接执行命令麻烦的多。



另外,有时也需要进入容器中,查看服务的运行情况。



为了方便的做这些事情,我开发了一个工具dockerbox,可以很方便的做到这些事情。



dockerbox的详情及使用方法请见:https://github.com/ligang1109/dockerbox





配置开机运行



最后再说下如何配置开机启动。 我使用虚拟机搭建的开发环境,所以配置这个会省事好多,我使用用了systemd:





ligang@vm-xUbuntu16 ~ $ ll /lib/systemd/system/dstart.service
-rw-r--r-- 1 root root 229 43 21:35 /lib/systemd/system/dstart.service

ligang@vm-xUbuntu16 ~ $ cat /lib/systemd/system/dstart.service
[Unit]
Description=Docker Container Starter
After=network.target docker.service
Requires=docker.service

[Service]
ExecStart=/usr/local/bin/dbox -dconfPath=/home/ligang/.dconf.json start all

[Install]
WantedBy=multi-user.target


dbox请参考dockerbox的使用方法。





结束语



上面说的是我现在使用的开发环境搭建方法,有兴趣爱折腾的同学不妨试试看,如果你有更好的方法,也希望能分享给我。



原文链接:使用Docker高效搭建开发环境

Kubernetes IN Docker - local clusters for testing Kubernetes

老马 发表了文章 • 0 个评论 • 288 次浏览 • 2019-06-04 21:52 • 来自相关话题

Brief Kind(Kubernetes IN Docker是一个用来快速创建 ...查看全部

Brief



Kind(Kubernetes IN Docker是一个用来快速创建和测试kubernetes的工具,Kind把环境的依赖降低到了最小,仅需要机器安装了Docker即可。



Kind 可以做什么?




  • 快速创建一个或多个Kubernetes集群(几分钟)

  • 支持HA Master部署高可用的Kubernetes集群

  • 支持从源码构建并部署一个Kubernetes集群

  • 可以快速低成本体验一个最新的Kubernetes集群,并支持Kubernetes的绝大部分功能

  • 支持本地离线运行一个多节点集群



Kind 有哪些优势?




  • 最小的安装依赖,仅需要安装Docker即可

  • 使用快速简单,使用kind cli工具即可快速创建集群

  • 使用container来 mockkubernetes node

  • 内部使用kubeadm的官方主流部署工具

  • 使用了containerd

  • 通过了CNCF官方的k8s conformance测试





Usage





GO111MODULE="on" go get sigs.k8s.io/kind@v0.3.0 && kind create cluster




How it work



Kind 使用一个 container 来模拟一个 Node,在 Container 里面跑了 systemd ,并用 systemd 托管了 kubelet 以及 containerd,然后容器内部的 kubelet 把其他 Kubernetes 组件,比如 kube-apiserver、etcd、CNI 等组件跑起来。



可以通过配置文件的方式,来通过创建多个 container 的方式,来模拟创建多个 Node,并以这些 Node 来构建一个多节点的 Kubernetes 集群。



Kind 内部使用了 kubeadm 这个工具来做集群的部署,包括 ha master 的高可用集群,也是借助 kubeadm 提供的aplha特性提供的。同时,在 HA Master 下,额外部署了一个 Nginx 用来提供负载均衡 vip。





Build Images



Kind 的镜像分为两个,一个 Node 镜像,一个 Base 镜像。



Node 镜像



Node 镜像的构建比较复杂,目前是通过运行 Base 镜像,并在 Base 镜像内执行操作,再保存此容器内容为镜像的方式来构建的,包含的操作有:




  • 构建 Kubernetes 相关资源(比如二进制文件和镜像)

  • 运行一个用于构建的容器

  • 把构建的 Kubernetes 相关资源复制到容器里

  • 调整部分组件配置参数,以支持在容器内运行

  • 预先拉去运行环境需要的镜像

  • 通过 docker commit 方式保存当前的构建容器为 node 镜像



具体的逻辑,可以参考node.go



Base 镜像



Base 镜像目前使用了 Ubuntu 19.04 作为基础镜像,做了下面的调整:




  • 安装 systemd 相关的包,并调整一些配置以适应在容器内运行

  • 安装 Kubernetes 运行时的依赖包,比如 conntrack、socat、CNI

  • 安装容器



运行环境,比如 containerd、crictl




  • 配置自己的 ENTRYPOINT 脚本,以适应和调整容器内运行的问题



具体的逻辑,可以参考构建的Dockerfile





Create Cluster



Kind 创建集群的基本过程为:




  1. 根据传入的参数,来创建 container,分为 control node 和 worker node 两种(如果是 ha master,还有一个 loadbalancer node)

  2. 如果需要,配置 loadbalancer 的配置,主要是 Nginx 配置文件

  3. 生成 kubeadm 配置

  4. 对于第一个控制节点,使用 kubeadm init 初始化单节点集群

  5. 配置安装 CNI 插件

  6. 配置存储(实际是安装了一个使用 hostpath 的 storageclass)

  7. 其他的控制节点,通过kubeadm join --experimental-control-plane的方式来扩容控制节点

  8. 通过 kubeadm join 扩容其他的工作节点

  9. 等待集群创建完成

  10. 生成访问配置,打印使用帮助具体的创建流程,可以参考代码create.go



这里关于每个容器,是如何作为 Node 跑起来的,可以简单讲解些原理:



根据不同的角色,调用不同的函数创建节点nodes.go





// TODO(bentheelder): remove network in favor of []cri.PortMapping when that is in
func (d *nodeSpec) Create(clusterLabel string) (node *nodes.Node, err error) {
// create the node into a container (docker run, but it is paused, see createNode)
// TODO(bentheelder): decouple from config objects further
switch d.Role {
case constants.ExternalLoadBalancerNodeRoleValue:
node, err = nodes.CreateExternalLoadBalancerNode(d.Name, d.Image, clusterLabel, d.APIServerAddress, d.APIServerPort)
case constants.ControlPlaneNodeRoleValue:
node, err = nodes.CreateControlPlaneNode(d.Name, d.Image, clusterLabel, d.APIServerAddress, d.APIServerPort, d.ExtraMounts)
case constants.WorkerNodeRoleValue:
node, err = nodes.CreateWorkerNode(d.Name, d.Image, clusterLabel, d.ExtraMounts)
default:
return nil, errors.Errorf("unknown node role: %s", d.Role)
}
return node, err
}


节点(容器)创建时,通过配置 –privileged,挂载 tmpfs,修改主机名等,来运行节点create



func createNode(name, image, clusterLabel, role string, mounts []cri.Mount, extraArgs ...string) (handle *Node, err error) {
runArgs := []string{
"-d", // run the container detached
"-t", // allocate a tty for entrypoint logs
// running containers in a container requires privileged
// NOTE: we could try to replicate this with --cap-add, and use less
// privileges, but this flag also changes some mounts that are necessary
// including some ones docker would otherwise do by default.
// for now this is what we want. in the future we may revisit this.
"--privileged",
"--security-opt", "seccomp=unconfined", // also ignore seccomp
"--tmpfs", "/tmp", // various things depend on working /tmp
"--tmpfs", "/run", // systemd wants a writable /run
// some k8s things want /lib/modules
"-v", "/lib/modules:/lib/modules:ro",
"--hostname", name, // make hostname match container name
"--name", name, // ... and set the container name
// label the node with the cluster ID
"--label", clusterLabel,
// label the node with the role ID
"--label", fmt.Sprintf("%s=%s", constants.NodeRoleKey, role),
}

// pass proxy environment variables to be used by node's docker deamon
proxyDetails := getProxyDetails()
for key, val := range proxyDetails.Envs {
runArgs = append(runArgs, "-e", fmt.Sprintf("%s=%s", key, val))
}

// adds node specific args
runArgs = append(runArgs, extraArgs...)

if docker.UsernsRemap() {
// We need this argument in order to make this command work
// in systems that have userns-remap enabled on the docker daemon
runArgs = append(runArgs, "--userns=host")
}

err = docker.Run(
image,
docker.WithRunArgs(runArgs...),
docker.WithMounts(mounts),
)

// we should return a handle so the caller can clean it up
handle = FromName(name)
if err != nil {
return handle, errors.Wrap(err, "docker run error")
}

return handle, nil
}


More



Kind是一个比较简单有趣的项目,Kind的scope定的比较明确和具体,也定的比较小,其实借助 Kind 或者 Kind 的思想,可以做更多的事情,比如:




  • 在单节点部署自己的上层平台

  • 借助容器 mock 节点的方式,优化现有的测试方案

  • 自动化的部署测试

  • 自动化的 e2e 测试



原文链接:https://mp.weixin.qq.com/s/Vhr0ml1wI1BIoxKqKGTXRg

58集团云平台架构实践与演进

玻璃樽 发表了文章 • 0 个评论 • 457 次浏览 • 2019-06-04 19:23 • 来自相关话题

【编者的话】在17年底,我们分享了《高可用Docker容器云在58集团的实践》这篇文章,对整个容器云基础环境搭建与应用选型进行了详细介绍,本文是在该文章基础之上的进阶篇,是针对具体业务场景的落地解决方案。如果对基础环境选型比较感兴趣,可以查看上篇文章,在本文的 ...查看全部
【编者的话】在17年底,我们分享了《高可用Docker容器云在58集团的实践》这篇文章,对整个容器云基础环境搭建与应用选型进行了详细介绍,本文是在该文章基础之上的进阶篇,是针对具体业务场景的落地解决方案。如果对基础环境选型比较感兴趣,可以查看上篇文章,在本文的最后会附上相关文章的链接。对于上篇文章讨论过的内容,本文将不再进行详细讨论。后续每个月,云团队都会选择平台中某一具体领域的相关工作进行详细讨论与分享,欢迎大家关注。大家想了解哪方面的实现方案与细节,可进行相应留言。
#背景

通过容器化技术,58云计算平台主要解决以下几个问题:

  1. 资源利用率低:通过云化技术可以将资源利用率提升至原有的3-4倍,甚至更高。
  2. 服务扩容效率低:将传统扩容的时间从小时级别降低为分钟级别。
  3. 上线流程不规范:基于同一的镜像模板,约束整个上线过程。

为了解决上述问题,云团队通过技术选型与反复论证最终决定基于Docker与Kubernetes体系构建整个容器云环境。

云计算平台的发展历程如下:
1.png

#整体架构

58云计算平台的整体架构如下:
2.png

所有容器云的架构都是相似的,这里不做赘述,具体可查看上篇文章。

云计算平台承载了集团90%以上的业务流量,作为核心的服务管理与上线系统,它并不是独立运作的,为了保证整个上线流程的一致性与流畅度,满足业务日常管理与维护的通用需求,云平台与集团内部多个核心系统与组件进行了相应的对接与联动。
3.jpg

在项目管理方面,云平台与代码管理系统、项目管理等系统进行了内部对接,实现了代码从编译到生成镜像,再到环境部署的完全自动化。

在运维方面,云平台与CMDB、服务树、监控系统等多个运维系统进行了打通与整合,保证整个运维体系的完整性与用户体验的一致性。

在基础组件方面,集团内部的服务治理体系与常用的中间件系统都针对云平台进行了相应的改造,以适配云平台的工作模式,同时云平台也集成了现有的数据收集组件,保证数据流与用户习惯在云化前后是一致的。

云平台的使用方面,平台定义了四套环境,四套环境基于唯一的镜像仓库,这使得同一个代码版本可以在不同的环境之间进行流转,在保证代码唯一性的同时也使得相关环境的变更不会传递到下一个环境。
4.png

#架构演进

构建一个适配多种业务场景的容器云有很多细节需要考虑,云团队做了很多工作,由于篇幅关系,这里主要和大家分享58云计算平台在“网络架构”和“服务发现”两个核心组件上的架构实践与演进。
##网络架构

在网络架构方面,通过对比常用的六种容器组网模型,云计算平台选择“bridge+vlan”的方式作为基础网络模型。
5.png

原生bridge网络模型存在两个明显的缺点:IP利用率低、缺少网络限速。

为了解决IP地址利用率低的问题,云平台基于docker的CNM接口开发了网络插件,支持多个宿主间共享同一个容器网段,也支持IP地址在不同的宿主间复用。
6.png

这种网络模式下,分配给业务实例的IP地址是随机的,实例每次重启IP地址都可能会发生变更。在推进业务云化的早期,这种模式被业务所接受,但是随着云化进程的不断深入,业务方面提出了更高的要求:固定IP。

业务需求来源于真实的案例:有些服务要求IP不能发生变化,特别是某些依赖于第三方外部接口的服务。同时集团内部很多系统都是以IP固定为前提,如果IP随机分配,这些现有系统将不可用或很难用,极大的影响用户体验。如果IP固定的需求不能被满足,业务云化将很难推进下去。所以在18年4月份,云平台对网络架构进行了升级,支持了固定IP模式。网络架构的升级很好的保证了业务云化的进程。
7.png

固定IP的网络架构基于Kubernetes的CNI接口实现,增加了IP控制器模块,业务每次扩缩容时,都会与IP控制器交互,进行IP的变更。在业务正常升级流程中,归属于业务的IP将不会发生变化。依托于腾讯机房的网络支撑,平台将容器网段的路由规则下发到交换机,实现了容器的全网漂移,而不仅仅局限于固定的交换机。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

针对网络限速的需求,云平台结合容器网络虚拟化的特性,基于自研监控与tc工具实现了容器网络限速的能力。
8.jpg

标准的tc工具只支持单向限速,即出口流量限速。单方向限速无法满足业务的实际需求。在深入研究容器网络虚拟化的原理后,我们发现,容器在做虚拟化时,会创建一个网卡对,他们是对等的网络设备,在宿主机上体现为veth,在容器内部体现为eth0。基于这个特性,我们实现了双向限速:即对两块网卡同时做出口流量限速。由于是对等网卡,所以对veth做出口流量限速就相当于对eth0做入口流量限速。

在网络限速的基础上,云平台又进行了多维度的完善,分别支持动态限速、秒级限速与弹性限速等网络应用场景,极大的实现带宽复用。
##服务发现

服务发现是容器云平台中非常核心的服务组件,是流量的入口,也是服务对外暴露的接口。云平台中IP动态分配,节点弹性伸缩,需要有一套自动变更负载均衡器的方式。对于后端服务,集团有成熟的服务治理框架与体系,在云化过程中,中间件团队对服务治理体系进行了改造使其可以适配不断变化的云环境。对于前端服务,集团是基于Nginx做负载均衡,并且没有一套支持IP自动变更的架构。为了实现前端服务的云化,云平台团队与运维团队共同设计了全新的服务发现架构。

在调研阶段,云团队也调研了Kubernetes自带的服务发现机制,这一机制无法提供复杂的负载均衡策略,并且无法满足业务在特殊场景下需要对部分节点摘除流量的需求,所以最终我们否定了这一方案。
9.png

这是业界典型的服务发现架构。服务端和负载均衡器通过Consul进行解耦。服务注册在Consul中,负载均衡器通过Watch Consul中目录的变化来实时感知节点变更。在云化的早期,为了满足快速上线的需求,云平台对集团内部的Java Web框架进行了修改,使得其以心跳的方式自动注册到Consul中。这很好的解决了负载均衡器自动感知业务变更的问题以及云化过程中的流量灰度问题。

这一架构也引入了新的问题:调试问题与多语言扩展问题。由于是Java框架代理业务注册到Consul中,这使得活跃节点无法被下掉流量从而进行调试,但实际场景中很多故障调试需要下掉流量后才能进行。对Java语言的支持是通过修改框架来完成的,而集团中还有很多其他类型语言,比如PHP、Node.js和Go等。对每种接入语言或框架都需要以修改代码的方式才能接入到云平台中来,开发成本高并且对业务不友好。Consul被直接暴露给服务方,也增加了Consul的安全风险。

基于此,在18年4月,云平台对整个服务发现架构进行了升级。
10.png

新的服务发现架构中,业务与Consul中间增加了Proxy代理层,代理层通过Watch Kubernetes事件实时感知业务节点信息的变化,配合健康检查功能可以保证业务节点在变更时流量无损。基于代理层,任意语言的程序不需要做任何修改,仅通过简单配置即可接入到云平台。服务的注册与发现托管到云平台,Consul组件对业务透明,开发人员使用更友好。
#复盘与反思

回顾这两年来的容器云架构演进过程与业务云化历程,复盘遇到的棘手问题及其解决方案,与大家共同探讨。

在整个云平台的设计之初,我们有如下的设计考量:
11.png

容器云都会面临一个问题:对于业务来说,是容器还是虚拟机?虚拟机是业务习惯的使用模式,业务更容易接受。容器是以服务为核心,服务存在即存在,服务关闭即销毁,不再是虚拟机以机器为核心的模式。思虑再三,最终决定以容器的模式来定位,全新的平台提供全新的服务模式与体验。

虽然是以容器为核心,但容器中可运行单进程也可运行多进程。我们规范容器中只运行一个业务进程,防止由于运行多进程而导致业务之间相互干扰情况的发生,这类问题很难进行排查与定位。

去Agent化:在传统物理机模式下,一台物理机上可能会有多个Agent:运维管控Agent、监控Agent、业务自定义Agent等。云化后面临一个现实的问题:我们需要在每个容器中都安装这些Agent么?如果每个容器中都集成这些Agent,对云平台来说是简单的,这没有任何工作量。但是一台物理机上可能会运行上百个容器,这些Agent数量也会大量膨胀,带来系统负载的增加的问题。基于此,云平台投入大量的精力来实现容器的无Agent化,即在所有功能正常运行的前提下,Agent只运行在宿主上,不会运行在容器中。

业务云化过程中云团队遇到了很多问题案例,并形成自己独特的解决方案。这里选择了几个有代表性的案例,与大家进行分享。
12.png

服务启动耗CPU过高是云化早期时遇到的棘手问题。这个问题产生的原因很多:Java的语言特性导致JVM是在运行中逐步进行优化的;内部的很多开发框架都是在流量过来时才初始化链接;某些业务资源也是在流量过来时才进行初始化。当流量分发过来时,多种资源同时初始化导致服务需要大量的CPU资源,超过了平台为其分配的CPU配额,服务出现大量超时与抛弃。这一问题的简单解法是多分配CPU资源,但从长远角度来看这会导致资源利用无法有效把控,也会影响弹性调度的效果。最终,我们从两个维度解决这一问题:在调用方增加预热策略,流量逐步分发过来,CPU资源使用更平滑;在服务方增加预热方法,默认初始化链接等资源,同时引导用户进行自定义的初始化。

在容器监控维度方面,由于默认的监控数据是基于随机采样的分钟级数据,导致当服务出现短期秒级的CPU波动时,监控系统无法捕获,从而影响问题的排查与定位。针对这一问题,云平台对监控维度进行了深化,增加了容器级别秒级监控,实时采集每分钟的最大值上报,便于问题的排查与跟踪。
13.png

由于Cgroups只对CPU和内存资源进行了隔离与限速,并没有对系统负载进行隔离,如果容器中运行进程或线程数过多,会导致宿主机整体的负载波动,进而对上面的所有服务都会造成影响。

云平台增加了两个维度的控制策略:容器级别的最大线程数控制,防止由于单个容器线程数过多会对宿主造成影响。宿主级别的过载保护,当宿主上的负载过高时,过载保护策略会自动触发宿主上的容器进行漂移,直至负载降至合理的范围。

云平台必须关闭swap交换分区,这是一个深刻的经验教训。在云化的早期,云平台的交换分区没有关闭,部分服务经常出现由于使用交换分区而导致的耗时随机抖动,这类问题很难排查,耗费了大量的精力。

58云计算平台核心软件版本变迁过程如下:
14.png

#后记

容器云在58集团的实践与探索过程中,云团队做了很多技术选型与优化工作,也进行了很多技术方案的架构演进工作,究其根本原因是58集团是发展了十多年的互联网公司,它有自己独特的业务场景和成熟的开发体系与基础组件,容器云在集团内部起步较晚,内部系统很难为云平台做适配,只能云平台通过不断演进来适配不停发展的业务场景。这也是在较有规模的互联网公司做基础架构与创业公司的区别和挑战所在。所幸在团队的共同努力下,我们用1年多的时间完成了集团流量服务的云化工作。目前,集团内部还有很多服务需要云化,未来的挑战更大。

原文链接:https://mp.weixin.qq.com/s/gJM5-DByvMH54QhVsJ5HEA

把数据库放入Docker是一个好主意吗?

Andy_Lee 发表了文章 • 0 个评论 • 372 次浏览 • 2019-06-04 11:33 • 来自相关话题

一个未打补丁的漏洞影响所有版本的 Docker !

齐达内 发表了文章 • 0 个评论 • 236 次浏览 • 2019-05-31 15:49 • 来自相关话题

所有版本的Docker目前都容易受到一种竞态条件(race condition)的攻击,这种竞态条件可能使攻击者对主机系统上的任何文件拥有读取权限和写入权限。概念验证代码已发布。 该漏洞类似CVE-2018-15664,它为黑客修改资 ...查看全部
所有版本的Docker目前都容易受到一种竞态条件(race condition)的攻击,这种竞态条件可能使攻击者对主机系统上的任何文件拥有读取权限和写入权限。概念验证代码已发布。

该漏洞类似CVE-2018-15664,它为黑客修改资源路径提供了一个机会窗口,这个机会窗口出现在路径被解析之后,但被分配的程序开始对资源进行操作之前的时间点。这被称为检查时间/使用时间(TOCTOU)类型的漏洞。
##访问主机上的文件
究其核心,该漏洞源于FollowSymlinkInScope函数,该函数容易受到基本的TOCTOU攻击的影响。这个函数的目的是如同进程在Docker容器的内部那样对待进程,以一种安全的方式来解析指定的路径。

并不立即针对解析的路径进行操作,而是“稍微过一段时间进行操作”。攻击者可以推测这个时间差,添加一条符号链接(symlink)路径,该路径可能最终解析拥有root权限的主机。

这可以通过“docker cp”实用程序来实现,该实用程序允许在容器和本地文件系统之间复制内容。早在2014年就出现过类似的漏洞。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

Suse的高级软件工程师Aleksa Sarai在安全公告中写道:“据我所知,对这种攻击没有任何有意义的防护(除了不允许对运行中的容器执行docker cp,但这只有助于应对通过FollowSymlinkInScope实现的特定攻击。)除非你通过AppArmor限制了Docker守护进程,否则它会影响主机文件系统。”
##应对方法和漏洞脚本
在Hacker News网站上,一则讨论帖子提供了潜在的应对方法,不过它们有赖于实际环境的背景和目的。

Sarai提议的一种应对办法是修改“chrootarchive”,以便归档操作在安全的环境中运行,其中root是容器“rootfs”。这需要更改Docker的核心部分,因此这不可行。

其次的办法是在使用文件系统时暂停容器。这无法阻止所有攻击,但可以防御较基本的攻击。补丁已向上游提交,目前仍在审核中。

这位工程师还编写了两个漏洞脚本:一个脚本用于读取访问,另一个用于写入访问,其中二进制代码通过运行“a RENAME_EXCHANGE of a symlink to "/" and an empty directory in a loop”,试图达到竞态条件。Sarai称,这两个脚本的目的是将文件复制到含有修改后的符号链接的路径,或从该路径复制文件。

从处于竞态条件下的主机系统读取任意内容的攻击代码其成功率不到1%。这个成功率可能看起来很低,但实际上相当于等待10秒钟攻击就能得逞。

有了将资源写入到主机上的脚本,就有可能“仅用极少的迭代就可以覆盖主机文件系统”。

在公开宣布这个漏洞之前,Sarai与Docker安全团队讨论了这个问题,最后得出了披露是合理的这一结论。

原文链接:https://mp.weixin.qq.com/s/1d18fBAKJj5LYymSzpFECA

Docker环境的持续部署优化实践

JetLee 发表了文章 • 0 个评论 • 213 次浏览 • 2019-05-31 14:48 • 来自相关话题

#背景介绍 那年公司快速成长,频繁上线新项目,每上线一个项目,就需要新申请一批机器,初始化,部署依赖的服务环境,一个脚本行天下。 那年项目发展如火如荼,A项目流量暴增马上给A扩机器,B项目上线新功能又要扩容B,上线新 ...查看全部
#背景介绍

那年公司快速成长,频繁上线新项目,每上线一个项目,就需要新申请一批机器,初始化,部署依赖的服务环境,一个脚本行天下。

那年项目发展如火如荼,A项目流量暴增马上给A扩机器,B项目上线新功能又要扩容B,上线新项目没资源了,就先下线处于流量低峰的C项目主机。

每天日夜加班,疲于奔命。

那年得知了Docker能拯救我于水火,遂决定为了荣誉(发际线)而战。

为了快速落地以及尽量降低引入Docker对整个CICD流程的影响,用最小的改动把Docker加入到了我们上线的流程中,流程变化参考下图:

1.png


那年容器编排江湖混战,K8S还不流行,加之时间精力有限,技术实力也跟不上,生产环境没敢贸然上线编排,单纯在之前的主机上跑了Docker,主要解决环境部署和扩容缩容的问题,Docker上线后也确实解决了这两块的问题,还带来了诸如保证开发线上环境一致性等额外惊喜。

但Docker的运用也并不是百利而无一害,将同步代码的方式转变成打包镜像、更新容器也带来了上线时间的增长,同时由于各个环境配置文件的不同也没能完全做到一次打包多环境共用,本文主要介绍我们是如何对这两个问题进行优化的。
#Python多线程使用

分析了部署日志,发现在整个部署过程中造成时间增长的主要原因是下载镜像、重启容器时间较长。

整个部署程序由Python开发,核心思想是用paramiko模块来远程执行ssh命令,在还没有引入Docker的时候,发布是rsyslog同步代码,单线程滚动重启服务,上线Docker后整个部署程序逻辑没有大改,只是把同步代码重启服务给换成了下载镜像重启容器,代码大致如下:
import os
import paramiko

# paramiko.util.log_to_file("/tmp/paramiko.log")
filepath = os.path.split(os.path.realpath(__file__))[0]


class Conn:
def __init__(self, ip, port=22, username='ops'):
self.ip = ip
self.port = int(port)
self.username = username

self.pkey = paramiko.RSAKey.from_private_key_file(
filepath + '/ssh_private.key'
)

def cmd(self, cmd):
ssh = paramiko.SSHClient()

try:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(self.ip, self.port, self.username, pkey=self.pkey, timeout=5)
except Exception as err:
data = {"state": 0, "message": str(err)}
else:
try:
stdin, stdout, stderr = ssh.exec_command(cmd, timeout=180)
_err_list = stderr.readlines()

if len(_err_list) > 0:
data = {"state": 0, "message": _err_list}
else:
data = {"state": 1, "message": stdout.readlines()}
except Exception as err:
data = {"state": 0, "message": '%s: %s' % (self.ip, str(err))}
finally:
ssh.close()

return data


if __name__ == '__main__':
# 演示代码简化了很多,整体逻辑不变

hostlist = ['10.82.9.47', '10.82.9.48']
image_url = 'ops-coffee:latest'

for i in hostlist:
print(Conn(i).cmd('docker pull %s' % image_url))
# 在镜像下载完成后进行更新容器的操作,代码类似省略了

全部都是单线程操作,可想效率就不会很高,为什么不用多线程?主要还是考虑到服务的可用性,一台服务器更新完成再更新下一台服务器直到所有服务器更新完成,单线程滚动更新最大程度保证服务可用,如果同时所有服务器进行更新,那么服务重启过程中无法对外提供服务,系统会有宕机的风险,且当时项目规模都很小,忽略掉了这个时间的增加,随着项目越来越多,规模越来越大,不得不重新思考这块的优化。

引入多线程势在必行,那么多线程该如何应用呢?从服务整体可用性考虑,把下载镜像跟重启容器两个操作拆分,下载镜像不影响服务正常提供,完全可以采用多线程,这样整个下载镜像的时间将大大缩短,优化后代码如下:
import threading
# 再导入上一个示例里边的Conn类

class DownloadThread(threading.Thread):

def __init__(self, host, image_url):
threading.Thread.__init__(self)
self.host = host
self.image_url = image_url

def run(self):
Conn(self.host).cmd('docker login -u ops -p coffee hub.ops-coffee.cn')
r2 = Conn(self.host).cmd('docker pull %s' % self.image_url)
if r2.get('state'):
self.alive_host = self.host
print('---->%s镜像下载完成' % self.host)
else:
self.alive_host = None
print('---->%s镜像下载失败,details:%s' % (self.host, r2.get('message')))

def get_result(self):
return self.alive_host


if __name__ == '__main__':
# 演示代码简化了很多,整体逻辑不变

hostlist = ['10.82.9.47', '10.82.9.48']
image_url = 'ops-coffee:latest'

threads = []
for host in hostlist:
t = DownloadThread(host, image_url)
threads.append(t)

for t in threads:
t.start()

for t in threads:
t.join()

alive_host = []
for t in threads:
alive_host.append(t.get_result())
[size=16] 多线程下载镜像结束
[/size]

print('---->本项目共有主机%d台,%d台主机下载镜像成功' % (len(hostlist), len(alive_host)))

重启容器就不能这么简单粗暴的多线程同时重启了,上边也说了,同时重启就会有服务宕机的风险。线上服务器都有一定的冗余,不能同时重启那么可以分批重启嘛,每次重启多少?分析了流量情况,我们想到了一个算法,如果项目主机少于8台,那么就单线程滚动重启,也用不了太长时间,如果项目主机大于8台,那么用项目主机数/8向上取整,作为多线程重启的线程数多线程重启,这样差不多能保证项目里边有80%左右的主机一直对外提供服务,降低服务不可用的风险,优化后的代码如下:
import threading
from math import ceil
# 再导入上一个示例里边的Conn类

class DeployThread(threading.Thread):
def __init__(self, thread_max_num, host, project_name, environment_name, image_url):
threading.Thread.__init__(self)
self.thread_max_num = thread_max_num
self.host = host
self.project_name = project_name
self.environment_name = environment_name
self.image_url = image_url

def run(self):
self.smile_host = []
with self.thread_max_num:
Conn(self.host).cmd('docker stop %s && docker rm %s' % (self.project_name, self.project_name))

r5 = Conn(self.host).cmd(
'docker run -d --env ENVT=%s --env PROJ=%s --restart=always --name=%s -p 80:80 %s' % (
self.environment_name, self.project_name, self.project_name, self.image_url)
)

if r5.get('state'):
self.smile_host.append(self.host)
print('---->%s镜像更新完成' % (self.host))
else:
print('---->%s服务器执行docker run命令失败,details:%s' % (self.host, r5.get('message')))

# check镜像重启状态 and 重启失败需要回滚代码省略

def get_result(self):
return self.smile_host


if __name__ == '__main__':
# 演示代码简化了很多,整体逻辑不变

alive_host = ['10.82.9.47', '10.82.9.48']
image_url = 'ops-coffee:latest'

project_name = 'coffee'
environment_name = 'prod'

# alive_host / 8 向上取整作为最大线程数
thread_max_num = threading.Semaphore(ceil(len(alive_host) / 8))

threads = []
for host in alive_host:
t = DeployThread(thread_max_num, host, project_name, environment_name, image_url)
threads.append(t)

for t in threads:
t.start()

for t in threads:
t.join()

smile_host = []
for t in threads:
smile_host.append(t.get_result())

print('---->%d台主机更新成功' % (len(smile_host)))

经过以上优化我们实测后发现,一个28台主机的项目在优化前上线要花10分钟左右的时间,优化后只要2分钟左右,效率提高80%。
#多环境下配置文件的处理

我们采用了项目代码打包进镜像的镜像管理方案,开发、测试、预发布、生产环境配置文件都不同,所以即便是同一个项目不同的环境都会单独走一遍部署发布流程打包镜像,把不同环境的配置打包到不同的镜像中,这个操作太过繁琐且没有必要,还大大增加了我们的上线时间。

用过Kubernetes的都知道,Kubernetes中有专门管理配置文件的ConfigMap,每个容器可以定义要挂载的配置,在容器启动时自动挂载,以解决打包一次镜像不同环境都能使用的问题,对于没有用到Kubernetes的要如何处理呢?配置中心还是必不可少的。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

我们处理不同配置的整体思路是,在Docker启动时传入两个环境变量ENVT和PROJ,这两个环境变量用来定义这个容器是属于哪个项目的哪个环境,Docker的启动脚本拿到这两个环境变量后利用confd服务自动去配置中心获取对应的配置,然后更新到本地对应的位置,这样就不需要把配置文件打包进镜像了。

以一个纯静态只需要Nginx服务的项目为例。

Dockerfile如下:
FROM nginx:base

COPY conf/run.sh /run.sh
COPY webapp /home/project/webapp

CMD ["/run.sh"]

run.sh脚本如下:
#!/bin/bash
/etc/init.d/nginx start && \
sed -i "s|/project/env/|/${PROJ}/${ENVT}/|g" /etc/confd/conf.d/conf.toml && \
sed -i "s|/project/env/|/${PROJ}/${ENVT}/|g" /etc/confd/templates/conf.tmpl && \
confd -watch -backend etcd -node=http://192.168.107.101:2379 -node=http://192.168.107.102:2379 || \
exit 1

Docker启动命令:
'docker run -d --env ENVT=%s --env PROJ=%s --restart=always --name=%s -p 80:80 %s' % (
self.environment_name, self.project_name, self.project_name, self.image_url)

做到了一次镜像打包多环境共用,上线时也无需再走一次编译打包的流程,只需更新镜像重启容器即可,效率明显提高。
#写在最后


* 缺少编排的容器是没有灵魂的,继续推进编排工具的运用将会是2019年工作的重点。
* 实际上我们在Docker改造稳定后,内网开发测试环境部署了一套k8s集群用到现在已经一年多的时间比较稳定。
* 线上用到了多云环境,一部分线上项目已经使用了基于Kubernetes的容器编排,当然还有一部分是我上边介绍的纯Docker环境。

原文链接:https://mp.weixin.qq.com/s/xnBehfSlZ3J02xb0GFuGDw
条新动态, 点击查看
jamlee

jamlee 回答了问题 • 2014-12-15 15:34 • 9 个回复 不感兴趣

docker-registry 的搭建

赞同来自:

经过尝试,在我的<strong>客户端</strong>运行: ``` docker -d ----insecure-registry 10.0.1.44:5000 ``... 显示全部 »
经过尝试,在我的<strong>客户端</strong>运行: ``` docker -d ----insecure-registry 10.0.1.44:5000 ``` 成功,问题完美解决。
这样做是可以的,但还是不建议。 首先,您提到了Docker单进程模式带来管理配置的复杂化,这一点相信很多人都赞成。 但是如果想把您的系统用一个Docker Container来Dockerize化,也就是用容器来替代虚拟机,那么反过来讲,您的初衷是什么?您... 显示全部 »
这样做是可以的,但还是不建议。 首先,您提到了Docker单进程模式带来管理配置的复杂化,这一点相信很多人都赞成。 但是如果想把您的系统用一个Docker Container来Dockerize化,也就是用容器来替代虚拟机,那么反过来讲,您的初衷是什么?您关心的Docker比VM有优势的地方在哪里?能想到的点可能是:节约资源,少跑一个OS的资源?启动快捷,水平扩展方便?相信这些都不是那么切中Docker能解决的痛点。当然Docker作为您公司产品的发布,会是一个很好的点。 但是如果使用Docker单进程模式的话,实际上将您的系统进行了一次简单的重构,从单点跨向分布式。复杂化了容器的管理配置,实际上目前有不少开源的内容已经在做这方面的内容了,如fig等;同时带来的其他好处还有:为您的系统的多模块各自提供隔离的运行环境;没有“把鸡蛋都放在一个篮子里”;运维管理方便(相比单VM模式下)。
郭蕾

郭蕾 回答了问题 • 2015-03-25 21:53 • 13 个回复 不感兴趣

Docker如何为企业产生价值?

赞同来自:

健波,你这个问题其实Docker公司的人最清楚,Docker是一个开源项目,也是一个产品。如果你是产品经理,肯定要知道它的目标用户以及目标场景,对吧?正好Docker两周年的庆祝活动上,(https://twitter.com/jpetazzo)分享了(htt... 显示全部 »
健波,你这个问题其实Docker公司的人最清楚,Docker是一个开源项目,也是一个产品。如果你是产品经理,肯定要知道它的目标用户以及目标场景,对吧?正好Docker两周年的庆祝活动上,(https://twitter.com/jpetazzo)分享了(http://www.slideshare.net/jpetazzo/docker-automation-for-the-rest-of-us),Jérôme是Docker公司员工,做了很多事,大家应该知道,所以他说的其实就是Docker公司说的。 我们曾经为Docker想了N多使用场景,但你有没有发现,官方对自己的定位一直很简单:an open platform to build, ship, and run any app, anywhere。看到了吧?这是官方定义的Docker,这不也是他们的初衷吗? Docker真正火起来的原因是什么了?后面Jérôme又说了一个话题:What can Docker do for me,我贴几个重要的: * Get a well-defined, reproducible environment * Define this environment in a Dockerfile * Build this Dockerfileinto a container image * Run this container image anywhere 好的产品一定是解决了某一个需求,而Docker也不例外,所以我觉得Docker的亮点是可以通过镜像来定义环境。
其实这些(https://docs.docker.com)都有介绍。 (https://docs.docker.com/machine/):解决的是操作系统异构安装Docker困难的问题,没有Machine的时候,CentOS是一种,Ubuntu又是一种,A... 显示全部 »
其实这些(https://docs.docker.com)都有介绍。 (https://docs.docker.com/machine/):解决的是操作系统异构安装Docker困难的问题,没有Machine的时候,CentOS是一种,Ubuntu又是一种,AWS又是一种。有了Machine,所有的系统都是一样的安装方式。 (https://docs.docker.com/swarm/):我们有了Machine就意味着有了docker环境,但是那是单机的,而通常我们的应用都是集群的。这正是Swarm要做的事情,给你提供docker集群环境和调度策略等。 (https://docs.docker.com/compose/):有了环境,我们下一步要做什么?部署应用啊。然后我们需要docker run image1、docker run image2...一次一次不厌其烦的重复这些操作,每次都写大量的命令参数。Compose简化了这个流程,只需要把这些内容固话到docker-compose.yml中。 目前Machine、Swarm、Compose已经可以结合使用,创建集群环境,简单的在上面部署应用。但是还不完善,比如对于有link的应用,它们只能跑在Swarm集群的一个机器上,即使你的集群有很多机器。可以参考(http://dockerone.com/question/105)。 SocketPlane是Docker最近收购的产品,猜想应该是为了强化Docker的网络功能,比如提供原生跨主机的网络定制、强化Swarm和Compose的结合等。
icebolt

icebolt 回答了问题 • 2015-04-14 12:00 • 16 个回复 不感兴趣

DockOne技术沙龙有感:向高焕堂老师致敬

赞同来自:

参与的人太少了,期待大家都积极的参与到话题里面来。 高焕堂老师,提到了赚第二阶段的钱的问题。有的程序员可能觉得赚钱模式,离自己太远,自己还是专心搞技术。我谈谈我的想法,我觉得 没有纯技术,技术一定是为了解决现实的问题的解决问题的技术也必须有财力支撑才能不断... 显示全部 »
参与的人太少了,期待大家都积极的参与到话题里面来。 高焕堂老师,提到了赚第二阶段的钱的问题。有的程序员可能觉得赚钱模式,离自己太远,自己还是专心搞技术。我谈谈我的想法,我觉得 没有纯技术,技术一定是为了解决现实的问题的解决问题的技术也必须有财力支撑才能不断的成长。作为为技术而工作的开发维护者,成为盈利产品的参与者才会有发展,不止是薪酬回报问题,只有盈利的项目,你花心思做的东西,才会有生命。 所以我觉得开发维护者关心的应该不止于技术,而应该积极的参与到盈利模式的探讨中来。
我可以提供360小水滴,同步直播。 我有个建议是,效仿docker官方也成立一个docker中国技术委员会,委员会成员来自各个公司,大家有责任提供更多得资源和分享给meetup。
我可以提供360小水滴,同步直播。 我有个建议是,效仿docker官方也成立一个docker中国技术委员会,委员会成员来自各个公司,大家有责任提供更多得资源和分享给meetup。
DockOne

DockOne 回答了问题 • 2015-07-23 17:09 • 5 个回复 不感兴趣

国内有哪些Docker大牛?

赞同来自:

<ol><li><strong>新浪微博</str... 显示全部 »
<ol><li><strong>新浪微博</strong> 陈飞 </li><li><strong>华为</strong> 黄强</li><li><strong>天云软件</strong> 贾琨</li><li><strong>希云cSphere</strong> 王利俊</li><li><strong>雪球</strong> 高磊</li><li><strong>云雀科技</strong> 左玥</li><li><strong>数人科技</strong> 肖德时</li><li><strong>IBM</strong> 杨博</li><li><strong>DaoCloud</strong> 孙宏亮</li><li><strong>浙江大学</strong> 张磊、孙健波</li><li><strong>京东</strong> 徐新坤</li><li><strong>时速云</strong> 王磊</li><li><strong>华为</strong> 马全一</li></ol>........ 上面是比较出名的,这里是@Fiona 整理的一份国内的(https://github.com/fiona-hall/Docker-Warriors)的名单,供参考。仅作交流。
李颖杰

李颖杰 回答了问题 • 2015-10-16 14:08 • 6 个回复 不感兴趣

docker书籍

赞同来自:

给你推荐本电子书http://yuedu.baidu.com/ebook/d817967416fc700abb68fca1?_searchquery=Docker%C8%EB%C3%C5%CA%B5%D5%BD
给你推荐本电子书http://yuedu.baidu.com/ebook/d817967416fc700abb68fca1?_searchquery=Docker%C8%EB%C3%C5%CA%B5%D5%BD
这种情况下,可以在docker构建时,临时禁用cache
这种情况下,可以在docker构建时,临时禁用cache
yingz

yingz 回答了问题 • 2016-04-07 17:19 • 4 个回复 不感兴趣

使用docker exec进入容器,无法读取环境变量问题

赞同来自:

`/etc/profile`中的变量不是自动就`export`的,完整的os在启动过程会有启动程序依次读取系统和用户的配置文件,但在容器里就没有这一步了,所以要自己导出才可以。 最直接的办法是用 `run` 或`create`的`-e`参数通过命令行在容器外指... 显示全部 »
`/etc/profile`中的变量不是自动就`export`的,完整的os在启动过程会有启动程序依次读取系统和用户的配置文件,但在容器里就没有这一步了,所以要自己导出才可以。 最直接的办法是用 `run` 或`create`的`-e`参数通过命令行在容器外指定环境变量;当然也可以写一个`entrypoint`脚本,在其中`export` 那些基本不发生变化的环境变量。 对于你说的这种情况,建议直接在镜像里`rm` 旧版本的 php ;-P 请参考 (https://docs.docker.com/engine/reference/run/#env-environment-variables)

在Docker上编译OpenJDK 8

李颖杰 发表了文章 • 0 个评论 • 136 次浏览 • 2019-06-18 11:06 • 来自相关话题

以前曾经试过在VMware上安装Linux,但是每次都不顺利,例如Linux环境,预装依赖软件,OpenJDK源码的选择等环境都会遇到问题,一旦失败再重新开始挺费时间的,现在用Docker就省事多了,镜像和容器的创建和删除都比较简单干净,专心做好Dock ...查看全部

以前曾经试过在VMware上安装Linux,但是每次都不顺利,例如Linux环境,预装依赖软件,OpenJDK源码的选择等环境都会遇到问题,一旦失败再重新开始挺费时间的,现在用Docker就省事多了,镜像和容器的创建和删除都比较简单干净,专心做好Dockerfile就行,下面我们一起来实践一下吧。



本次实战用到的所有文件,已经打包到GitHub上,路径是git@github.com:zq2599/centos7_build_openjdk8.git,欢迎您来使用(git clone git@github.com:zq2599/centos7_build_openjdk8.git)。



本次编译实战的基本步骤如下:


编写的Dockerfile中要做的如下的事情:




  1. 安装依赖的软件;

  2. 把OpenJDK的源码复制到镜像中;



在编写Dockerfile之前要做三个重要的选择,如下:




  1. Linux:我选择了CentOS 7,之前试过Ubuntu 16.04,但是在Configure的时候提示”freetype”没有安装,我按照提示去装了,再次Configure的时候继续提示”freetype”没有安装……(此问题现在还没解决,如果您解决过相同问题,请您告诉一下解决方法,谢谢了!)

  2. OpenJDK源码,这次要编译的是OpenJDK 8,源码的下载地址在这里

  3. Bootstrap JDK:即编译时要用到的JDK,下载了OpenJDK的源码后,解压开可以看到“README-builds.html”这个文件,里面有对Bootstrap JDK的描述:




看得出,需要安装JDK 7来编译OpenJDK 8的源码。



OK,关键问题都已确认,即将开始Dockerfile制作,不过制作之前还有个小问题需要先想好:本次我打算把制作镜像所需的Dockerfile和依赖文件都放到GitHub上去,这样做的好处有两个:




  1. 读者们从Git上clone下来之后直接执行Docker build就能在本地构建镜像;

  2. daocloud.io网站上支持通过执行GitHub目录的方式在线构建镜像,后面我们会实践在daocloud.io上构建镜像并部署到腾讯云或者阿里云服务器上。



上传到Gith=Hub时,除了Dockerfile,还要上传的文件有两个:JDK 1.7和OpenJDK 8源码,都超过了100M,如下图:


这就麻烦了,GitHub上传文件的时候,单个文件不能超过100M,否则push的时候会被服务器拒绝,解决这个问题有两个办法:



1、构建镜像的时候不要把这两个文件复制到镜像中了,改为在Dockerfile中通过执行wget命令将这两个文件分别下载到镜像中,OpenJDK的下载路径是:http://www.java.net/download/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip,而OpenJDK 1.7的下载路径就难办了,Oracle上下载历史版本的时候,是要做登录操作的,这个在Dockerfile中难以实现,找到了一个下载地址:https://mirror.its.sfu.ca/mirror/CentOS-Third-Party/NSG/common/x86_64/jdk-7u80-linux-x64.rpm,但是下载速度很慢,最少一个小时以上了,所以wget这种方法看似简单,但下载文件耗时实在太长;



2、第二种方法比较简单易用,就是在Mac或者Linux上先用split命令将文件分割成多个,再上传到GitHub上,在Dockerfile中有对应的命令将分割后的文件恢复成分割前的原文件,具体的分割命令如下:





split -b 50m jdk-7u71-linux-x64.rpm jdkrpm-


这个命令是将jdk-7u71-linux-x64.rpm分割成不超过50m的多个文件,分割后的文件以jdkrpm-作为文件名的前缀,如下图:


OpenJDK的源码用如下命令分割:





split -b 50m openjdk-8-src-b132-03_mar_2014.zip openjdksrc-


Dockerfile中,从分割文件恢复以上两个原文件的命令为:





cat jdkrpm-* > jdk-7u71-linux-x64.rpm
cat openjdksrc-* > openjdk-8-src-b132-03_mar_2014.zip


以上就是两种处理大文件的方法,本文用的是第二种,即先分割上传到Git,在Dockerfile中将已分割文件恢复成原文件再使用。



好了,前期的准备工作已经做完了,现在可以编写Dockerfile文件了,整个文件中要做的事情列出如下:



1、安装依赖软件,例如libXtst-devel,libXt-devel等等,这些都是编译前的Configure命令要检查的,检查不过无法进行编译;



2、把分割后的OpenJDK源码复制到镜像文件中,再合成,再解压;



3、安装JDK 7,把分割后的安装文件复制到镜像中合成,然后安装;



4、为了方便用户进入容器后快速开始编译,我们做了一个shell脚本start_make.sh,把这个脚本也要复制到镜像中,内容如下:





#!/bin/bash
$WORK_PATH/$OPENJDK_SRC_DIR/configure
echo "start make"
cd $WORK_PATH/$OPENJDK_SRC_DIR
make all ZIP_DEBUGINFO_FILES=0 DISABLE_HOTSPOT_OS_VERSION_CHECK=OK


5、清理无用的文件,例如OpenJDK源码的压缩文件,JDK 7的安装文件等;



按照以上步骤,最终写出的Dockerfile文件如下:





# Docker image of compile and build enviroment for openjdk8
# VERSION 0.0.1
# Author: bolingcavalry

#基础镜像使用CentOS 7
FROM centos:centos7

#作者
MAINTAINER BolingCavalry

#定义工作目录
ENV WORK_PATH /usr/local

#定义JDK 1.7的文件名
ENV JDK_RPM_FILE jdk-7u71-linux-x64.rpm

#定义OpenJDK源码的文件名
ENV OPENJDK_SRC_ZIP openjdk-8-src-b132-03_mar_2014.zip

#定义解压缩后的文件名
ENV OPENJDK_SRC_DIR openjdk

#yum更新
RUN yum -y update

#安装工具集
RUN yum -y groupinstall "Development Tools"

#安装即将用到的软件
RUN yum -y install unzip libXtst-devel libXt-devel libXrender-devel cups-devel freetype-devel alsa-lib-devel which

#把分割过的JDK 1.7安装文件复制到工作目录
COPY ./jdkrpm-* $WORK_PATH/

#用本地分割过的文件恢复原有的JDK 1.7的安装文件
RUN cat $WORK_PATH/jdkrpm-* > $WORK_PATH/$JDK_RPM_FILE

#本地安装JDK 1.7
RUN yum -y localinstall $WORK_PATH/$JDK_RPM_FILE

#把分割过的OpenJDK 8的源码压缩包复制到工作目录
COPY ./openjdksrc-* $WORK_PATH/

#用本地分割过的文件恢复原有的OpenJDK 8的源码压缩包
RUN cat $WORK_PATH/openjdksrc-* > $WORK_PATH/$OPENJDK_SRC_ZIP

#解压缩源码
RUN unzip $WORK_PATH/$OPENJDK_SRC_ZIP -d $WORK_PATH

#复制启动编译的shell
COPY ./start_make.sh $WORK_PATH/$OPENJDK_SRC_DIR/

#给执行文件增加可执行权限:Configure文件
RUN chmod a+x $WORK_PATH/$OPENJDK_SRC_DIR/configure

#给执行文件增加可执行权限:启动编译文件
RUN chmod a+x $WORK_PATH/$OPENJDK_SRC_DIR/start_make.sh

#删除分割文件
RUN rm $WORK_PATH/jdkrpm-*

#删除分割文件
RUN rm $WORK_PATH/openjdksrc-*

#删除JDK安装包文件
RUN rm $WORK_PATH/$JDK_RPM_FILE

#删除OpenJDK源码压缩文件
RUN rm $WORK_PATH/$OPENJDK_SRC_ZIP


至此,镜像文件制作所需的材料都已经齐全了,如下图:


现在让我们开始制作镜像吧,打开终端,进入Dockerfile所在目录,执行命令:





docker build -t bolingcavalryopenjdk:0.0.1 .


因为要在线安装不少的软件,所以可能耗时会略长,和网络带宽有关,我在住处用家庭网络大概10分钟左右构建成功,执行目录docker images查看镜像,新的镜像文件已经生成了,如下图:


现在启动一个容器试试吧:





docker run --name=jdk001 -idt bolingcavalryopenjdk:0.0.1


容器已经启动,再执行以下命令进入容器:





docker exec -it jdk001 /bin/bash


进去后,直接到/usr/local/openjdk目录下,执行./start_make.sh,开始编译了,整个过程的耗时和当前电脑的硬件配置有关,我用i7处理器的Mac Pro 15大约要用20多分钟,编译结束后会有类似下图的输出:


这时候去/usr/local/openjdk目录下看看,发现多了一个build目录,这里面就是编译好的结果,如下图:


build目录下只有一个linux-x86_64-normal-server-release目录,再进去就能看到JDK目录了,如下图:


进入/usr/local/openjdk/build/linux-x86_64-normal-server-release/jdk/bin目录,会发现里面有Java文件,执行./java -version输出如下:


新的JDK信息已经打印出来了,OpenJDK Runtime Enviroment信息已经说明了这是个最新构建的JDK环境。



至此我们的本次实战就结束了,面对如此干净的编译环境和现成的源码,读者您是否有一种改动一番源码的冲动,然后构建一个个性化的属于自己的JDK,建议您自己动手修改和编译OpenJDK源码,以加深对JVM的认识。

使用Docker高效搭建开发环境

齐达内 发表了文章 • 0 个评论 • 497 次浏览 • 2019-06-04 23:00 • 来自相关话题

【编者的话】Docker作为轻量级的基于容器的解决方案,它对系统侵入性低,容易移植,天生就适合做复杂业务部署和开发环境搭建,今天给大家带来的是奇虎360开发是如何使用Docker高效搭建开发环境的。 作为一个平时喜 ...查看全部

【编者的话】Docker作为轻量级的基于容器的解决方案,它对系统侵入性低,容易移植,天生就适合做复杂业务部署和开发环境搭建,今天给大家带来的是奇虎360开发是如何使用Docker高效搭建开发环境的。



作为一个平时喜欢折腾的开发人员,我喜欢尝试各种环境,使用感兴趣的各种开源软件。



同时,我也是有一些相对的小洁癖,很喜欢Linux中权限最小化原则,我也不喜欢自己的环境中有太多不知道的东西。



做了多年的Web开发,我接触到的环境大致如下:




  1. 操作系统从CentOS 5到CentOS 7;

  2. Web Server从Apache到Nginx;

  3. 开发语言从最初的PHP 5.2到PHP 7,又到现在主要使用Go,马上还会开始接触C++;

  4. 数据库从MySQL 5.1到现在的5.7,前阵子又开始折腾MariaDB;

  5. Cache选型从Memcache到Redis;

  6. 队列用过Kafka,去年开始大量使用NSQ;



公司虽然有专门负责部署、运维这些服务的同学,但我在开发的时候,还是喜欢自己来搭建这些东西,因为这样通常可以对使用到的服务有更多的认识,也能帮助自己使用的更好。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态



今天我就来和大家分享下我是如何高效的搭建好自己的开发环境的。





搭建前说明



这里先说明一点,对每个开源软件,我几乎都是自己编译部署的,而不会使用类似yum install这种方式,也很少直接下载官方编译好的二进制包,这都是为了能多深入了解用到的开源软件。



但一些依赖的动态库文件,如zlib等,还有编译工具,如GCC、make等,我都是通过方便的yum install这种方式直接安装的,否则会累死。





传统做法



我在很长的一段时间内,都是把每个软件的编译、安装过程写成一个脚本,之后再需要用的时候直接运行脚本即可,但这样的方式,通常会遇到下面这些问题:




  1. 脚本只能在我当时的操作系统环境下运行。记得当时购买过不同服务商的VPS,虽然不同VPS我都使用同样的Linux发行版,但脚本通常都不能一键跑完。这也是没办法,因为每个VPS服务商都会制作自己的操作系统镜像版本。

  2. 操作系统升级,如CentOS 5 - 6,或是换为Ubuntu,这样基本上脚本都跑不了。

  3. 软件升级,如MySQL 5.2 - 5.6,构建工具改为CMake,依赖库改变或升级。

  4. 如果某个软件依赖的公共库版本和其它软件不同,且公共库升级后和旧版不兼容,那你就只能为这个软件单独编译公共库了,如果只是普通的公共库还好,但如果是所需要的编译工具版本不同,那可就惨了。



上面这些问题,如果你想每个发行版维护一个脚本,那会累死,因为一旦你每次想升级一个软件,难道每个发行版都要编译一遍吗?这就变成了收获价值很低的体力劳动了。



由于喜欢折腾的个性,我对操作系统的升级以及软件包版本的升级又经常发生,所以一直以来,我都在寻找一个好方法,能很方便的维护好自己的开发环境,尽量做到只=新东西只为它工作一次,最后我找到了Docker,目前我都是用它来搭建自己的开发环境的。





Docker做法



先概括介绍下我的方法:




  1. 让每个软件运行在容器中,因为运行的容器环境是可以固定下来的,所以编译安装脚本写一个就可以了。

  2. 代码使用数据卷的方式加载到需要的容器中。

  3. 因为是开发环境,所以网络方面使用最简单的–net=host。

  4. 将镜像的创建、容器的启动维护在Git项目中,并抽象出统一的构建过程,很方面的做到新软件接入,新机器部署。



下面用实例来说明把:





示例Nginx环境构建



我将构建过程放到Git中:https://gitee.com/andals/docker-nginx



Readme中记录了构建所需要执行的脚本命令,大家访问上面的网址就可以看到,这里我简单介绍下项目的结构:





├── Dockerfile        //创建镜像的Dockerfile
├── pkg //编译好的二进制包,可以直接使用,此外软件运行的一些配置文件或第三方包也放在这里
│ ├── conf
│ │ ├── fastcgi.conf
│ │ ├── http.d
│ │ ├── include
│ │ ├── koi-utf
│ │ ├── koi-win
│ │ ├── logrotate.conf
│ │ ├── logrotate.d
│ │ ├── mime.types
│ │ ├── Nginx.conf
│ │ ├── scgi_params
│ │ ├── uwsgi_params
│ │ └── win-utf
│ ├── luajit-2.0.3.tar.gz
│ └── Nginx-1.8.1.tar.gz
├── README.md
├── script //里面放构建脚本
│ ├── build_image.sh //构建镜像使用
│ ├── build_pkg.sh //编译软件包时使用
│ ├── init.sh //容器启动时执行
│ └── pre_build.sh //软件依赖的共享库,编译和构建时都会用到
└── src //编译时需要的软件源码
├── modules
│ ├── ngx_devel_kit-0.2.19.tgz
│ ├── ngx_echo-0.53.tgz
│ └── ngx_lua-0.9.7.tgz
├── Nginx-1.8.1.tar.gz
└── openssl-1.0.2h.tar.gz




DockerFile说明



Dockerfile结构如下:





FROM andals/CentOS:7
MAINTAINER ligang

LABEL name="Nginx Image"
LABEL vendor="Andals"

COPY pkg/ /build/pkg/
COPY script/ /build/script/

RUN /build/script/build_image.sh

CMD /build/script/init.sh


整个构建框架为:




  1. 把构建需要的包(PKG目录中)放到镜像中

  2. 把构建脚本放到镜像中

  3. 执行构建脚本

  4. 容器启动时,执行init.sh,里面启动相应的服务



Readme.md中记录了执行构建的命令和容器运行命令,示例运行如下:





ligang@vm-xUbuntu16 ~/devspace/dbuild $ git clone git@gitee.com:andals/docker-Nginx.git Nginx
Cloning into 'Nginx'...
......

ligang@vm-xUbuntu16 ~/devspace/dbuild $ cd Nginx/
ligang@vm-xUbuntu16 ~/devspace/dbuild/Nginx $ ngxVer=1.8.1
ligang@vm-xUbuntu16 ~/devspace/dbuild/Nginx $ docker build -t andals/Nginx:${ngxVer} ./
Sending build context to Docker daemon 30.7MB
Step 1/8 : FROM andals/CentOS:7
......
Successfully built ea8147743031
Successfully tagged andals/Nginx:1.8.1

ligang@vm-xUbuntu16 ~/devspace/dbuild/Nginx $ docker run -d --name=Nginx-${ngxVer} --volumes-from=data-home -v /data/Nginx:/data/Nginx --net=host andals/Nginx:${ngxVer}
dbf3c0617eb34c4b1b4ea54c2961989612d5474db3b1acd1d717221e6e5cb516


说明: –volumes-from=data-home这个就是我放置代码的数据卷,我喜欢把代码放到$HOME下面,所以这个卷的相关信息如下:





ligang@vm-xUbuntu16 ~ $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
578912a08ea7 andals/CentOS:7 "echo Data Volumn Ho…" 9 days ago Exited (0) 9 days ago data-home
......

ligang@vm-xUbuntu16 ~ $ docker inspect 578912a08ea7
......
"Mounts": [
{
"Type": "bind",
"Source": "/home",
"Destination": "/home",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
......


/data/Nginx中放置Nginx的conf、log等,每个软件运行时的conf、log、data等我都统一放置在/data下面,如:





ligang@vm-xUbuntu16 ~ $ tree -d /data/ -L 1
/data/
├── mariadb
├── Nginx
└── redis

ligang@vm-xUbuntu16 ~ $ tree -d /data/Nginx/
/data/Nginx/
├── conf
│ ├── http.d
│ ├── include
│ └── logrotate.d
└── logs


启动容器时使用–net=host,作为开发环境简单实用 我就是通过这种方法完成了开发环境的构建,不再有多余的重复工作,并且新机器部署开发环境效率极高。



我目前用到的容器环境如下:





ligang@vm-xUbuntu16 ~ $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dbf3c0617eb3 andals/Nginx:1.8.1 "/bin/sh -c /build/s…" 33 minutes ago Up 33 minutes Nginx-1.8.1
3e31ef433298 andals/php:7.1.9 "/bin/sh -c /build/s…" 8 hours ago Up 8 hours php-7.1.9
360f94bf9c43 andals/jekyll:latest "/bin/sh -c /build/s…" 9 days ago Up 10 hours jekyll-latest
0a7d58d1ca5e andals/redis:4.0.8 "/bin/sh -c /build/s…" 9 days ago Up 10 hours redis-4.0.8
fdaa655b4a11 andals/samba:4.4.16 "/bin/sh -c /build/s…" 9 days ago Up 10 hours samba-4.4.16
6ad00a69befd andals/mariadb:10.2.14 "/bin/sh -c /build/s…" 9 days ago Up 10 hours mariadb-10.2.14
578912a08ea7 andals/CentOS:7 "echo Data Volumn Ho…" 9 days ago Exited (0) 9 days ago data-home




辅助工具dockerbox



使用Docker环境后,有个问题,就是没有办法很方便的和软件交互。 这是因为软件都执行在容器中,比如重启Nginx吧,需要下面这几步:




  1. 找到Nginx这个容器

  2. 进入Nginx这个容器

  3. 在容器里面再执行reload



也可以是:




  1. 找到Nginx这个容器

  2. 使用docker exec



但无论哪种方式,都比原先直接执行命令麻烦的多。



另外,有时也需要进入容器中,查看服务的运行情况。



为了方便的做这些事情,我开发了一个工具dockerbox,可以很方便的做到这些事情。



dockerbox的详情及使用方法请见:https://github.com/ligang1109/dockerbox





配置开机运行



最后再说下如何配置开机启动。 我使用虚拟机搭建的开发环境,所以配置这个会省事好多,我使用用了systemd:





ligang@vm-xUbuntu16 ~ $ ll /lib/systemd/system/dstart.service
-rw-r--r-- 1 root root 229 43 21:35 /lib/systemd/system/dstart.service

ligang@vm-xUbuntu16 ~ $ cat /lib/systemd/system/dstart.service
[Unit]
Description=Docker Container Starter
After=network.target docker.service
Requires=docker.service

[Service]
ExecStart=/usr/local/bin/dbox -dconfPath=/home/ligang/.dconf.json start all

[Install]
WantedBy=multi-user.target


dbox请参考dockerbox的使用方法。





结束语



上面说的是我现在使用的开发环境搭建方法,有兴趣爱折腾的同学不妨试试看,如果你有更好的方法,也希望能分享给我。



原文链接:使用Docker高效搭建开发环境

Kubernetes IN Docker - local clusters for testing Kubernetes

老马 发表了文章 • 0 个评论 • 288 次浏览 • 2019-06-04 21:52 • 来自相关话题

Brief Kind(Kubernetes IN Docker是一个用来快速创建 ...查看全部

Brief



Kind(Kubernetes IN Docker是一个用来快速创建和测试kubernetes的工具,Kind把环境的依赖降低到了最小,仅需要机器安装了Docker即可。



Kind 可以做什么?




  • 快速创建一个或多个Kubernetes集群(几分钟)

  • 支持HA Master部署高可用的Kubernetes集群

  • 支持从源码构建并部署一个Kubernetes集群

  • 可以快速低成本体验一个最新的Kubernetes集群,并支持Kubernetes的绝大部分功能

  • 支持本地离线运行一个多节点集群



Kind 有哪些优势?




  • 最小的安装依赖,仅需要安装Docker即可

  • 使用快速简单,使用kind cli工具即可快速创建集群

  • 使用container来 mockkubernetes node

  • 内部使用kubeadm的官方主流部署工具

  • 使用了containerd

  • 通过了CNCF官方的k8s conformance测试





Usage





GO111MODULE="on" go get sigs.k8s.io/kind@v0.3.0 && kind create cluster




How it work



Kind 使用一个 container 来模拟一个 Node,在 Container 里面跑了 systemd ,并用 systemd 托管了 kubelet 以及 containerd,然后容器内部的 kubelet 把其他 Kubernetes 组件,比如 kube-apiserver、etcd、CNI 等组件跑起来。



可以通过配置文件的方式,来通过创建多个 container 的方式,来模拟创建多个 Node,并以这些 Node 来构建一个多节点的 Kubernetes 集群。



Kind 内部使用了 kubeadm 这个工具来做集群的部署,包括 ha master 的高可用集群,也是借助 kubeadm 提供的aplha特性提供的。同时,在 HA Master 下,额外部署了一个 Nginx 用来提供负载均衡 vip。





Build Images



Kind 的镜像分为两个,一个 Node 镜像,一个 Base 镜像。



Node 镜像



Node 镜像的构建比较复杂,目前是通过运行 Base 镜像,并在 Base 镜像内执行操作,再保存此容器内容为镜像的方式来构建的,包含的操作有:




  • 构建 Kubernetes 相关资源(比如二进制文件和镜像)

  • 运行一个用于构建的容器

  • 把构建的 Kubernetes 相关资源复制到容器里

  • 调整部分组件配置参数,以支持在容器内运行

  • 预先拉去运行环境需要的镜像

  • 通过 docker commit 方式保存当前的构建容器为 node 镜像



具体的逻辑,可以参考node.go



Base 镜像



Base 镜像目前使用了 Ubuntu 19.04 作为基础镜像,做了下面的调整:




  • 安装 systemd 相关的包,并调整一些配置以适应在容器内运行

  • 安装 Kubernetes 运行时的依赖包,比如 conntrack、socat、CNI

  • 安装容器



运行环境,比如 containerd、crictl




  • 配置自己的 ENTRYPOINT 脚本,以适应和调整容器内运行的问题



具体的逻辑,可以参考构建的Dockerfile





Create Cluster



Kind 创建集群的基本过程为:




  1. 根据传入的参数,来创建 container,分为 control node 和 worker node 两种(如果是 ha master,还有一个 loadbalancer node)

  2. 如果需要,配置 loadbalancer 的配置,主要是 Nginx 配置文件

  3. 生成 kubeadm 配置

  4. 对于第一个控制节点,使用 kubeadm init 初始化单节点集群

  5. 配置安装 CNI 插件

  6. 配置存储(实际是安装了一个使用 hostpath 的 storageclass)

  7. 其他的控制节点,通过kubeadm join --experimental-control-plane的方式来扩容控制节点

  8. 通过 kubeadm join 扩容其他的工作节点

  9. 等待集群创建完成

  10. 生成访问配置,打印使用帮助具体的创建流程,可以参考代码create.go



这里关于每个容器,是如何作为 Node 跑起来的,可以简单讲解些原理:



根据不同的角色,调用不同的函数创建节点nodes.go





// TODO(bentheelder): remove network in favor of []cri.PortMapping when that is in
func (d *nodeSpec) Create(clusterLabel string) (node *nodes.Node, err error) {
// create the node into a container (docker run, but it is paused, see createNode)
// TODO(bentheelder): decouple from config objects further
switch d.Role {
case constants.ExternalLoadBalancerNodeRoleValue:
node, err = nodes.CreateExternalLoadBalancerNode(d.Name, d.Image, clusterLabel, d.APIServerAddress, d.APIServerPort)
case constants.ControlPlaneNodeRoleValue:
node, err = nodes.CreateControlPlaneNode(d.Name, d.Image, clusterLabel, d.APIServerAddress, d.APIServerPort, d.ExtraMounts)
case constants.WorkerNodeRoleValue:
node, err = nodes.CreateWorkerNode(d.Name, d.Image, clusterLabel, d.ExtraMounts)
default:
return nil, errors.Errorf("unknown node role: %s", d.Role)
}
return node, err
}


节点(容器)创建时,通过配置 –privileged,挂载 tmpfs,修改主机名等,来运行节点create



func createNode(name, image, clusterLabel, role string, mounts []cri.Mount, extraArgs ...string) (handle *Node, err error) {
runArgs := []string{
"-d", // run the container detached
"-t", // allocate a tty for entrypoint logs
// running containers in a container requires privileged
// NOTE: we could try to replicate this with --cap-add, and use less
// privileges, but this flag also changes some mounts that are necessary
// including some ones docker would otherwise do by default.
// for now this is what we want. in the future we may revisit this.
"--privileged",
"--security-opt", "seccomp=unconfined", // also ignore seccomp
"--tmpfs", "/tmp", // various things depend on working /tmp
"--tmpfs", "/run", // systemd wants a writable /run
// some k8s things want /lib/modules
"-v", "/lib/modules:/lib/modules:ro",
"--hostname", name, // make hostname match container name
"--name", name, // ... and set the container name
// label the node with the cluster ID
"--label", clusterLabel,
// label the node with the role ID
"--label", fmt.Sprintf("%s=%s", constants.NodeRoleKey, role),
}

// pass proxy environment variables to be used by node's docker deamon
proxyDetails := getProxyDetails()
for key, val := range proxyDetails.Envs {
runArgs = append(runArgs, "-e", fmt.Sprintf("%s=%s", key, val))
}

// adds node specific args
runArgs = append(runArgs, extraArgs...)

if docker.UsernsRemap() {
// We need this argument in order to make this command work
// in systems that have userns-remap enabled on the docker daemon
runArgs = append(runArgs, "--userns=host")
}

err = docker.Run(
image,
docker.WithRunArgs(runArgs...),
docker.WithMounts(mounts),
)

// we should return a handle so the caller can clean it up
handle = FromName(name)
if err != nil {
return handle, errors.Wrap(err, "docker run error")
}

return handle, nil
}


More



Kind是一个比较简单有趣的项目,Kind的scope定的比较明确和具体,也定的比较小,其实借助 Kind 或者 Kind 的思想,可以做更多的事情,比如:




  • 在单节点部署自己的上层平台

  • 借助容器 mock 节点的方式,优化现有的测试方案

  • 自动化的部署测试

  • 自动化的 e2e 测试



原文链接:https://mp.weixin.qq.com/s/Vhr0ml1wI1BIoxKqKGTXRg

58集团云平台架构实践与演进

玻璃樽 发表了文章 • 0 个评论 • 457 次浏览 • 2019-06-04 19:23 • 来自相关话题

【编者的话】在17年底,我们分享了《高可用Docker容器云在58集团的实践》这篇文章,对整个容器云基础环境搭建与应用选型进行了详细介绍,本文是在该文章基础之上的进阶篇,是针对具体业务场景的落地解决方案。如果对基础环境选型比较感兴趣,可以查看上篇文章,在本文的 ...查看全部
【编者的话】在17年底,我们分享了《高可用Docker容器云在58集团的实践》这篇文章,对整个容器云基础环境搭建与应用选型进行了详细介绍,本文是在该文章基础之上的进阶篇,是针对具体业务场景的落地解决方案。如果对基础环境选型比较感兴趣,可以查看上篇文章,在本文的最后会附上相关文章的链接。对于上篇文章讨论过的内容,本文将不再进行详细讨论。后续每个月,云团队都会选择平台中某一具体领域的相关工作进行详细讨论与分享,欢迎大家关注。大家想了解哪方面的实现方案与细节,可进行相应留言。
#背景

通过容器化技术,58云计算平台主要解决以下几个问题:

  1. 资源利用率低:通过云化技术可以将资源利用率提升至原有的3-4倍,甚至更高。
  2. 服务扩容效率低:将传统扩容的时间从小时级别降低为分钟级别。
  3. 上线流程不规范:基于同一的镜像模板,约束整个上线过程。

为了解决上述问题,云团队通过技术选型与反复论证最终决定基于Docker与Kubernetes体系构建整个容器云环境。

云计算平台的发展历程如下:
1.png

#整体架构

58云计算平台的整体架构如下:
2.png

所有容器云的架构都是相似的,这里不做赘述,具体可查看上篇文章。

云计算平台承载了集团90%以上的业务流量,作为核心的服务管理与上线系统,它并不是独立运作的,为了保证整个上线流程的一致性与流畅度,满足业务日常管理与维护的通用需求,云平台与集团内部多个核心系统与组件进行了相应的对接与联动。
3.jpg

在项目管理方面,云平台与代码管理系统、项目管理等系统进行了内部对接,实现了代码从编译到生成镜像,再到环境部署的完全自动化。

在运维方面,云平台与CMDB、服务树、监控系统等多个运维系统进行了打通与整合,保证整个运维体系的完整性与用户体验的一致性。

在基础组件方面,集团内部的服务治理体系与常用的中间件系统都针对云平台进行了相应的改造,以适配云平台的工作模式,同时云平台也集成了现有的数据收集组件,保证数据流与用户习惯在云化前后是一致的。

云平台的使用方面,平台定义了四套环境,四套环境基于唯一的镜像仓库,这使得同一个代码版本可以在不同的环境之间进行流转,在保证代码唯一性的同时也使得相关环境的变更不会传递到下一个环境。
4.png

#架构演进

构建一个适配多种业务场景的容器云有很多细节需要考虑,云团队做了很多工作,由于篇幅关系,这里主要和大家分享58云计算平台在“网络架构”和“服务发现”两个核心组件上的架构实践与演进。
##网络架构

在网络架构方面,通过对比常用的六种容器组网模型,云计算平台选择“bridge+vlan”的方式作为基础网络模型。
5.png

原生bridge网络模型存在两个明显的缺点:IP利用率低、缺少网络限速。

为了解决IP地址利用率低的问题,云平台基于docker的CNM接口开发了网络插件,支持多个宿主间共享同一个容器网段,也支持IP地址在不同的宿主间复用。
6.png

这种网络模式下,分配给业务实例的IP地址是随机的,实例每次重启IP地址都可能会发生变更。在推进业务云化的早期,这种模式被业务所接受,但是随着云化进程的不断深入,业务方面提出了更高的要求:固定IP。

业务需求来源于真实的案例:有些服务要求IP不能发生变化,特别是某些依赖于第三方外部接口的服务。同时集团内部很多系统都是以IP固定为前提,如果IP随机分配,这些现有系统将不可用或很难用,极大的影响用户体验。如果IP固定的需求不能被满足,业务云化将很难推进下去。所以在18年4月份,云平台对网络架构进行了升级,支持了固定IP模式。网络架构的升级很好的保证了业务云化的进程。
7.png

固定IP的网络架构基于Kubernetes的CNI接口实现,增加了IP控制器模块,业务每次扩缩容时,都会与IP控制器交互,进行IP的变更。在业务正常升级流程中,归属于业务的IP将不会发生变化。依托于腾讯机房的网络支撑,平台将容器网段的路由规则下发到交换机,实现了容器的全网漂移,而不仅仅局限于固定的交换机。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

针对网络限速的需求,云平台结合容器网络虚拟化的特性,基于自研监控与tc工具实现了容器网络限速的能力。
8.jpg

标准的tc工具只支持单向限速,即出口流量限速。单方向限速无法满足业务的实际需求。在深入研究容器网络虚拟化的原理后,我们发现,容器在做虚拟化时,会创建一个网卡对,他们是对等的网络设备,在宿主机上体现为veth,在容器内部体现为eth0。基于这个特性,我们实现了双向限速:即对两块网卡同时做出口流量限速。由于是对等网卡,所以对veth做出口流量限速就相当于对eth0做入口流量限速。

在网络限速的基础上,云平台又进行了多维度的完善,分别支持动态限速、秒级限速与弹性限速等网络应用场景,极大的实现带宽复用。
##服务发现

服务发现是容器云平台中非常核心的服务组件,是流量的入口,也是服务对外暴露的接口。云平台中IP动态分配,节点弹性伸缩,需要有一套自动变更负载均衡器的方式。对于后端服务,集团有成熟的服务治理框架与体系,在云化过程中,中间件团队对服务治理体系进行了改造使其可以适配不断变化的云环境。对于前端服务,集团是基于Nginx做负载均衡,并且没有一套支持IP自动变更的架构。为了实现前端服务的云化,云平台团队与运维团队共同设计了全新的服务发现架构。

在调研阶段,云团队也调研了Kubernetes自带的服务发现机制,这一机制无法提供复杂的负载均衡策略,并且无法满足业务在特殊场景下需要对部分节点摘除流量的需求,所以最终我们否定了这一方案。
9.png

这是业界典型的服务发现架构。服务端和负载均衡器通过Consul进行解耦。服务注册在Consul中,负载均衡器通过Watch Consul中目录的变化来实时感知节点变更。在云化的早期,为了满足快速上线的需求,云平台对集团内部的Java Web框架进行了修改,使得其以心跳的方式自动注册到Consul中。这很好的解决了负载均衡器自动感知业务变更的问题以及云化过程中的流量灰度问题。

这一架构也引入了新的问题:调试问题与多语言扩展问题。由于是Java框架代理业务注册到Consul中,这使得活跃节点无法被下掉流量从而进行调试,但实际场景中很多故障调试需要下掉流量后才能进行。对Java语言的支持是通过修改框架来完成的,而集团中还有很多其他类型语言,比如PHP、Node.js和Go等。对每种接入语言或框架都需要以修改代码的方式才能接入到云平台中来,开发成本高并且对业务不友好。Consul被直接暴露给服务方,也增加了Consul的安全风险。

基于此,在18年4月,云平台对整个服务发现架构进行了升级。
10.png

新的服务发现架构中,业务与Consul中间增加了Proxy代理层,代理层通过Watch Kubernetes事件实时感知业务节点信息的变化,配合健康检查功能可以保证业务节点在变更时流量无损。基于代理层,任意语言的程序不需要做任何修改,仅通过简单配置即可接入到云平台。服务的注册与发现托管到云平台,Consul组件对业务透明,开发人员使用更友好。
#复盘与反思

回顾这两年来的容器云架构演进过程与业务云化历程,复盘遇到的棘手问题及其解决方案,与大家共同探讨。

在整个云平台的设计之初,我们有如下的设计考量:
11.png

容器云都会面临一个问题:对于业务来说,是容器还是虚拟机?虚拟机是业务习惯的使用模式,业务更容易接受。容器是以服务为核心,服务存在即存在,服务关闭即销毁,不再是虚拟机以机器为核心的模式。思虑再三,最终决定以容器的模式来定位,全新的平台提供全新的服务模式与体验。

虽然是以容器为核心,但容器中可运行单进程也可运行多进程。我们规范容器中只运行一个业务进程,防止由于运行多进程而导致业务之间相互干扰情况的发生,这类问题很难进行排查与定位。

去Agent化:在传统物理机模式下,一台物理机上可能会有多个Agent:运维管控Agent、监控Agent、业务自定义Agent等。云化后面临一个现实的问题:我们需要在每个容器中都安装这些Agent么?如果每个容器中都集成这些Agent,对云平台来说是简单的,这没有任何工作量。但是一台物理机上可能会运行上百个容器,这些Agent数量也会大量膨胀,带来系统负载的增加的问题。基于此,云平台投入大量的精力来实现容器的无Agent化,即在所有功能正常运行的前提下,Agent只运行在宿主上,不会运行在容器中。

业务云化过程中云团队遇到了很多问题案例,并形成自己独特的解决方案。这里选择了几个有代表性的案例,与大家进行分享。
12.png

服务启动耗CPU过高是云化早期时遇到的棘手问题。这个问题产生的原因很多:Java的语言特性导致JVM是在运行中逐步进行优化的;内部的很多开发框架都是在流量过来时才初始化链接;某些业务资源也是在流量过来时才进行初始化。当流量分发过来时,多种资源同时初始化导致服务需要大量的CPU资源,超过了平台为其分配的CPU配额,服务出现大量超时与抛弃。这一问题的简单解法是多分配CPU资源,但从长远角度来看这会导致资源利用无法有效把控,也会影响弹性调度的效果。最终,我们从两个维度解决这一问题:在调用方增加预热策略,流量逐步分发过来,CPU资源使用更平滑;在服务方增加预热方法,默认初始化链接等资源,同时引导用户进行自定义的初始化。

在容器监控维度方面,由于默认的监控数据是基于随机采样的分钟级数据,导致当服务出现短期秒级的CPU波动时,监控系统无法捕获,从而影响问题的排查与定位。针对这一问题,云平台对监控维度进行了深化,增加了容器级别秒级监控,实时采集每分钟的最大值上报,便于问题的排查与跟踪。
13.png

由于Cgroups只对CPU和内存资源进行了隔离与限速,并没有对系统负载进行隔离,如果容器中运行进程或线程数过多,会导致宿主机整体的负载波动,进而对上面的所有服务都会造成影响。

云平台增加了两个维度的控制策略:容器级别的最大线程数控制,防止由于单个容器线程数过多会对宿主造成影响。宿主级别的过载保护,当宿主上的负载过高时,过载保护策略会自动触发宿主上的容器进行漂移,直至负载降至合理的范围。

云平台必须关闭swap交换分区,这是一个深刻的经验教训。在云化的早期,云平台的交换分区没有关闭,部分服务经常出现由于使用交换分区而导致的耗时随机抖动,这类问题很难排查,耗费了大量的精力。

58云计算平台核心软件版本变迁过程如下:
14.png

#后记

容器云在58集团的实践与探索过程中,云团队做了很多技术选型与优化工作,也进行了很多技术方案的架构演进工作,究其根本原因是58集团是发展了十多年的互联网公司,它有自己独特的业务场景和成熟的开发体系与基础组件,容器云在集团内部起步较晚,内部系统很难为云平台做适配,只能云平台通过不断演进来适配不停发展的业务场景。这也是在较有规模的互联网公司做基础架构与创业公司的区别和挑战所在。所幸在团队的共同努力下,我们用1年多的时间完成了集团流量服务的云化工作。目前,集团内部还有很多服务需要云化,未来的挑战更大。

原文链接:https://mp.weixin.qq.com/s/gJM5-DByvMH54QhVsJ5HEA

用 Docker 快速配置前端开发环境

翔宇 发表了文章 • 0 个评论 • 295 次浏览 • 2019-05-30 21:28 • 来自相关话题

学好Docker,你应该了解这些

老马 发表了文章 • 0 个评论 • 263 次浏览 • 2019-05-30 20:07 • 来自相关话题

Docker公司推出的三剑客:Machine、Compose和Swarm。这三件利器的出现,让Docker不仅支持单机的虚拟化,而且能支持更广泛的集群平台,提供更强大灵活的功能。本文通过简短的介绍,让对Docker三剑客有个初步了解。今后遇到Docker相关问 ...查看全部
Docker公司推出的三剑客:Machine、Compose和Swarm。这三件利器的出现,让Docker不仅支持单机的虚拟化,而且能支持更广泛的集群平台,提供更强大灵活的功能。本文通过简短的介绍,让对Docker三剑客有个初步了解。今后遇到Docker相关问题,知道用什么技术去解决。

docker-machine:解决Docker运行环境问题

Docker技术是基于Linux内核的cgroup技术实现的,那么问题来了,如果在非Linux平台上使用Docker技术需要依赖安装Linux系统的虚拟机。docker-machine就是Docker公司官方提出的,用于在各种平台上快速创建具有Docker服务的虚拟机的技术。可以把它理解为virtualbox或者VMware,最开始在Win7上用得比较多,但是Win10开始自带了hyper-v虚拟机,已经不再需要docker-machine了,Docker可以直接运行在安装了Linux系统得hyper-v上。

dcoker-compose:解决本地Docker容器编排问题

一般是通过yaml配置文件来使用它,这个docker-compose.yml文件里能记录多个容器启动的配置信息(镜像、启动命令、端口映射等),最后只需要执行docker-compose对应的命令,例如docker-compose up就会像执行脚本一样地批量创建和销毁容器。

docker-swarm:解决多主机多个容器调度部署得问题

Swarm是基于Docker平台实现的集群技术,他可以通过几条简单的指令快速的创建一个Docker集群,接着在集群的共享网络上部署应用,最终实现分布式的服务。Swarm技术不是很成熟,很多配置功能都无法实现,只能说是个半成品,目前更多的是使用Kubernetes来管理集群和调度容器。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

总结

如果你是在非Linux环境下考虑使用docker-machine,当然我更推荐使用hyper-v或者virtualbox。如果你需要同时操作多个容器,或者希望使用配置文件记录容器启动命令参数,那么推荐使用docker-compose。如果你需要在多台主机上部署Docker容器,并对其进行调度,那么Swarm是一种选择,当然更推荐Kubernetes。

原文链接:https://www.jianshu.com/p/b293d076b34b

火热的云原生到底是什么?一文了解云原生四要素!

阿娇 发表了文章 • 0 个评论 • 240 次浏览 • 2019-05-30 18:33 • 来自相关话题

所谓云原生,它不是一个产品,而是一套技术体系和一套方法论,而数字化转型是思想先行,从内到外的整体变革。更确切地说,它是一种文化,更是一种潮流,是云计算的一个必然导向。 随着虚拟化技术的成熟和分布式架构的普及,用来部署、管理和运行应 ...查看全部
所谓云原生,它不是一个产品,而是一套技术体系和一套方法论,而数字化转型是思想先行,从内到外的整体变革。更确切地说,它是一种文化,更是一种潮流,是云计算的一个必然导向。

随着虚拟化技术的成熟和分布式架构的普及,用来部署、管理和运行应用的云平台被越来越多的提及。IaaS、PaaS和SaaS是云计算的3种基本服务类型,它们是关注硬件基础设施的基础设施即服务、关注软件和中间件平台的平台即服务以及关注业务应用的软件即服务。

在容器技术、可持续交付、编排系统等开源社区的推动下,以及微服务等开发理念的带动下,应用上云已经是不可逆转的趋势。随着云化技术的不断进展,云原生的概念也应运而生。
#云原生概念的诞生

云原生(Cloud Native)的概念,由来自Pivotal的MattStine于2013年首次提出,被一直延续使用至今。这个概念是Matt Stine根据其多年的架构和咨询经验总结出来的一个思想集合,并得到了社区的不断完善,内容非常多,包括DevOps、持续交付(Continuous Delivery)、微服务(MicroServices)、敏捷基础设施(Agile Infrastructure)和12要素(The Twelve-Factor App)等几大主题,不但包括根据业务能力对公司进行文化、组织架构的重组与建设,也包括方法论与原则,还有具体的操作工具。采用基于云原生的技术和管理方法,可以更好地把业务生于“云”或迁移到云平台,从而享受“云”的高效和持续的服务能力。
1.jpg

The Twelve-Factor App

顾名思义,云原生是面向“云”而设计的应用,因此技术部分依赖于传统云计算的3层概念,基础设施即服务(IaaS)、平台即服务(PaaS)和软件即服务(SaaS),例如,敏捷的不可变基础设施交付类似于IaaS,用来提供计算网络存储等基础资源,这些资源是可编程且不可变的,直接通过API可以对外提供服务;有些应用通过PaaS服务本来就能组合成不同的业务能力,不一定需要从头开始建设;还有一些软件只需要“云”的资源就能直接运行起来为云用户提供服务,即SaaS能力,用户直接面对的就是原生的应用。
##云原生并不是一个产品

最近讨论云原生应用越来越多。关于云原生应用,简单地说,就是大多数传统的应用,不做任何改动,都是可以在云平台运行起来,只要云平台支持这个传统应用所运行的计算机架构和操作系统。只不过这种运行模式,仅仅是把虚拟机当物理机一样使用,不能够真正利用起来云平台的能力。

云并非把原先在物理服务器上跑的东西放到虚拟机里跑,真正的云化不仅是基础设施和平台的事情,应用也要做出改变,改变传统的做法,实现云化的应用——应用的架构、应用的开发方式、应用部署和维护技术都要做出改变,真正的发挥云的弹性、动态调度、自动伸缩……一些传统IT所不具备的能力。这里说的“云化的应用”也就是“云原生应用”。云原生架构和云原生应用所涉及的技术很多,如容器技术、微服务、可持续交付、DevOps等。
2.jpg

而云原生应用最大的特点就是可以迅速部署新业务。在企业里,提供新的应用程序环境及部署软件新版本通常所需时间以日、周甚至以月计算。这种速度严重限制了软件发布所能承受的风险,因为犯错及改错也需要花费同样的时间成本,竞争优势就会由此产生。

所以云原生不是一个产品,而是一套技术体系和一套方法论,而数字化转型是思想先行,从内到外的整体变革。更确切地说,它是一种文化,更是一种潮流,是云计算的一个必然导向。意义在于让云成为云化战略成功的基石,而不是障碍。它可以根据商业能力对公司进行重组的能力,既包含技术、也包含管理,可以说是一系列云技术和企业管理方法的集合,通过实践及与其他工具相结合更好地帮助用户实现数字化转型。
##云原生计算基金会(CNCF)

CNCF,即云原生计算基金会,2015年由谷歌牵头成立,基金会成员目前已有一百多企业与机构,包括亚马逊、微软、思科等巨头。

目前CNCF所托管的应用已达14个,下图为其公布的Cloud Native Landscape,给出了云原生生态的参考体系。
3.jpg

Cloud Native Landscape新版

CNCF(云原生计算基金会)认为云原生系统需包含的属性:

* 容器化封装:以容器为基础,提高整体开发水平,形成代码和组件重用,简化云原生应用程序的维护。在容器中运行应用程序和进程,并作为应用程序部署的独立单元,实现高水平资源隔离。
* 自动化管理:统一调度和管理中心,从根本上提高系统和资源利用率,同时降低运维成本。
* 面向微服务:通过松耦合方式,提升应用程序的整体敏捷性和可维护性。

正因为如此,你可以专注于创新,解决业务问题,而不是把时间花在“静态、不灵活的传统架构”存在的许多技术问题。
#云原生的四要素:持续交付、DevOps、微服务、容器

从云原生的概念中,我们总是能看到持续交付、DevOps、微服务、容器等技术的出现,那么它们到底是什么,这里引用Pivotal台湾云计算资深架构师的部分观点,为大家逐一揭开他们的神秘面纱!
4.jpg

##持续交付——缩小开发者认知,灵活开发方向

首先是持续交付,什么样的时候客户要求持续交付?敏捷开发要求持续交付,因为敏捷开发要求随时有一个版本可以上到大群环境,所以要持续交付。

而换句话说,持续交付就是不误时开发。举一个例子,有些公司非常喜欢谈需求,谈很久,可是开发只剩1/3时间就开发完成,然后交付,再上线运营。这就会碰到一个问题,就是你开始谈需求到最后交付产品的时间,短则三月,长则半年,这中间市场已经变化了,需求也随之变化了。因此市场上出现了新的想法,即是不是能够小步快跑,把交付的周期缩短一点,我可以实现快速交付,每次交付都可以重新确认方向,这样尽量避免与未来期待的落差。
5.jpg

用小步快跑的方式,打破瀑布式开发流程

那么问题来了,持续交付对于开发的人谈的需求、开发的方式有改变,那它对于开发有影响吗?如果说公司的开发团队一天可以交付五次,那研发团队要帮忙部署一次吗?现在公司大部分部署都是研发团队帮忙部署应用的,研发团队部署五次,要改版五次就需要部署一次,这是无法实现的。而且每次部署的时候都要面对停机,而实际公司的应用经不起一天停机五次部署,在互联网的思维之下,零宕机时间已经是现在企业的基本要求。于是“蓝绿部署”的概念营运而生。即在一个环境里面,第一版还在线上服务,第二版先做封测,封测完成后,让外面的流量进来一些,看log是不是开发人员要的,确认后再把全部的流量导到新的版本上。
6.jpg

蓝绿(Blue-Green)部署

但“蓝绿部署”在系统过多过复杂的情况下,在传统架构上实现非常困难,所以企业要做到zero down time的持续交付就需要有良好的平台與工具协助。因此,持续交付的优势在于,它可以缩小开发者认知,重新确认开发方向。
##微服务——内聚更强,更加敏捷

第二部分是微服务。微服务是什么?有客户表示,提供商出产品,客户把应用全部放上去,结果就是一个微服务。这种认知是错误的,因为微服务是一个架构的改变。那么微服务是怎么做的呢?它所面临的最大挑战是什么?

是切割。那么如何切割呢?其实这件事情早在1968年康威就提出了——康威定律,系统的服务划分应该是根据组织架构的功能来划分。1968年康威就提出了这个想法,我认为拿来做微服务的切割非常适用。
7.jpg

Going Agile - Breaking the monolith Conway's Law and Microservices

这样按照组织架构划分的优势在于:

  1. 内聚更强,所有遵循同一种业务准则的人内聚在一起,就容易解决问题。
  2. 服务解耦,变更容易,更加敏捷。当做到解耦合的时候,要变更就容易。所以微服务应该是切分成这个样子,由上而下来切,根据Function来切。

另外一个划分微服务的技巧,可以运用领域驱动设计(Domain Driven Design)的理论,而领域驱动设计亦可算是面向物件的一种设计思维;聚合可以让微服务划分更有依据,也让未來的系統变更具有弹性。值得一提的是领域驱动设计,也提供微服务中的事物问题。因为过去巨石应用进行两个报数的阶段,相当容易也常见,但在微服务架构中,如何在分散的服务中进行事物就显得相当困难。利用领域驱动设计的Event Souring进行设计,是目前最好的解決办法。

那么在什么情况下需要微服务?我认为有三个标准:

  1. 有HA(High Available)的需求需要微服务。
  2. 有性能调校的需求(例如:图片的呈现或者搜寻)需要微服务。
  3. 经常变更的需要微服务。

实际上,微服务需要关注的源代码范围比较小,使得各个服务解耦、变更容易,内聚更强,因为都会集中在服务里。另外,它更容易单独改版,因为微服务之间是用RESTful间接起来的,用RESTful只要API的界面不改,原则上则不会错,也更敏捷。

但微服务也会留下一些问题,例如App团队如何分工?环境怎么配合?如何实现自动化部署?
##容器技术——使资源调度、微服务更容易

再来看看容器。在机器上运行的容器只是主机操作系统上的一个进程,与任何其他进程无异。那么,为什么容器如此受欢迎呢?原因在于这个进程被隔离和限制的方式。这种方式很特殊,可简化开发和运维。

其实1979年就有容器技术,很多人会以为说Docker是不是等于容器,其实Docker不等于容器。容器的历史可追溯到Linux操作系统。容器利用了Linux的内核功能。Linux中容器的核心概念(cgroup、namespaces和filesystems)在独立的区域运行。容器的神奇之处在于将这些技术融为一体,以实现最大的便利性。

VMware之前的技术专家在2011年发展出一个技术,把这个技术贡献出来成立了一个Cloud Foundry基金会。Docker在2013年才开始有,而且它第一版是用SLC的技术去做的。后来陆续一路成长,使得为服务的实现更容易了。
8.jpg

从 Infra 角度来看技术演进

从上面这个表中可以看出,从左边开始,IaaS,虚拟化技术有了之后,刚刚提到的所谓第三代平台,这四个区块开发人员交付的内容不一样。所有的IaaS、CaaS、PaaS、FaaS一路的变化演进,对于客户的负担越到后面越小,而对于开发人员的想象力则愈发抽象。

大家一定会遇到下列这些计算,一个是所谓的单体应用,或者翻译成巨石应用。此外,你们一定会有一些批次的管理,另外就是所谓的数据库的部分,开始可能会有容器技术,像Kubernetes、Docker。

Docker是软件行业最受欢迎的软件容器项目之一。思科、谷歌和IBM等公司在其基础设施和产品中使用Docker容器。

Kubernetes是软件容器领域的另一个值得关注的项目。Kubernetes是一个允许自动化部署、管理和伸缩容器的工具。为了便于管理其容器,谷歌建立了Kubernetes。它提供了一些强大的功能,例如容器之间的负载均衡,重启失败的容器以及编排容器使用的存储。如果你想和更多 Kubernetes 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
9.jpg

容器生态图

容器为云原生应用程序增加了更多优势。使用容器,你可以将微服务及其所需的所有配置、依赖关系和环境变量移动到全新的服务器节点上,而无需重新配置环境,这样就实现了强大的可移植性。
##DevOps——以终为始,运维合一

10.png

最后让我们走向DevOps,它不是一种工具,DevOps其实要谈的是运维合一。

DevOps如果从字面上来理解只是Dev(开发人员)+Ops(运维人员),实际上,它是一组过程、方法与系统的统称,其概念从2009年首次提出发展到现在,内容也非常丰富,有理论也有实践,包括组织文化、自动化、精益、反馈和分享等不同方面。

首先,组织架构、企业文化与理念等,需要自上而下设计,用于促进开发部门、运维部门和质量保障部门之间的沟通、协作与整合,简单而言组织形式类似于系统分层设计。

其次,自动化是指所有的操作都不需要人工参与,全部依赖系统自动完成,比如上述的持续交付过程必须自动化才有可能完成快速迭代。再次,DevOps的出现是由于软件行业日益清晰地认识到,为了按时交付软件产品和服务,开发部门和运维部门必须紧密合作。

总之,DevOps强调的是高效组织团队之间如何通过自动化的工具协作和沟通来完成软件的生命周期管理,从而更快、更频繁地交付更稳定的软件。在内部沟通上,你可以想象DevOps是一个敏捷思維,是一个沟通的文化。当运营和研发有良好的沟通效率,才可以有更大的生产力。如果你的自动化程度够高,可以自主可控,工作负担降低,DevOps能够带来更好的工作文化、更高的工作效率。
#总结

综上所述,云原生的DevOps、平台、持续交付、微服务都是云原生不可或缺的一部分,需要以全局地眼光看待问题,脱离任何一个元素,对于企业来说都是“管中窥豹”、“一叶障目”,只有加以整合才能见到云原生的全局风貌。

面对业态各异的业务上云以及碎片化的物联网解决方案部署,利用云原生思维和模式,构建基于云原生的物联网平台以及解决方案,势必将加速企业,甚至整个社会的数字化转型。

原文链接:https://mp.weixin.qq.com/s/RaAyjfGacHc7xpRahpfv8Q

容器云平台基础架构方案设计思考

aoxiang 发表了文章 • 0 个评论 • 286 次浏览 • 2019-05-29 22:02 • 来自相关话题

【编者的话】本文是作者作为一名云计算运维工程师在参与容器云平台建设中的一些有感记录,此次主要针对基础架构技术方案设计方面谈谈自己的一些认知,如有描述不妥之处,还请多包涵并给予指正。 如今,在移动互联的大时代下,互联网公司金融业务的爆炸 ...查看全部
【编者的话】本文是作者作为一名云计算运维工程师在参与容器云平台建设中的一些有感记录,此次主要针对基础架构技术方案设计方面谈谈自己的一些认知,如有描述不妥之处,还请多包涵并给予指正。

如今,在移动互联的大时代下,互联网公司金融业务的爆炸式发展给传统金融行业带来了巨大的冲击和挑战,金融行业也纷纷顺应发展趋势,大力发展移动端业务和互金类型业务,因此对业务需求的响应速度有了更高的要求,越来越多传统应用架构,为了适应不断变化的业务需求,难以预估的访问量,而开始进行分布式改造、微服务改造,实现持续集成、持续部署、支持弹性伸缩、灰度发布、蓝绿部署等能力,容器云平台恰恰可以很好的支撑上述需求。容器技术是近些年来最火热的技术弄潮儿,我们关注他,肯定不止是因为它足够火热,需要任何一项技术,一定都是以更好服务于应用,让用户使用感受越来越美好为目的。

笔者在容器平台的建设过程中,参与了大量的技术讨论和思维碰撞,一个非常大的感触就是,容器云的知识栈非常长,想对平台有一个全局的透彻理解,要学习的东西非常之多,而且很多是跨领域的。作为一名云计算运维工程师,在这里也简单聊聊在平台基础架构设计中自己的一些认知。
#平台选型
在做IaaS时,即使经过了深度的定制化自动化改造,IaaS上的流程走完时也普遍是在交付时把带有应用软件及软件配置的一台虚拟机交到申请者手中,申请者要自己通过IP登录去部署应用,更不用说各应用组件之间的配合设置。而在容器平台里,从代码开发集成,到一个容器镜像里打包了应用和应用的运行时环境,加上容器的配置文件,一套完整流程走下来时,应用已经可以上线了,负载均衡、安全策略都可以具备了,可以说容器云平台是DevOps理论的最佳实现。所以,对于容器平台的建设,从初期就需要各技术团队紧密结合,了解互相的需求,平台相关技术的原理机制,才能共同设计好一个容器平台。如果你想和更多容器技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

而对于传统应用容器化改造,应用接入容器云平台一定是一个循序渐进的过程,更是一个不断演讲的过程。为了让应用尽可能平滑的接入,平台设计也应适当考虑业务原场景和使用习惯,尽可能的避免大幅度修改逻辑,不希望为了容器化而容器化,还是要因地制宜,互相找到一个平衡。虽做了很多技术实践经验的调研,但真正在自己的环境中落地时,大家都是摸着石头过河,要通过试点应用,积累应用改造的经验,运维的经验。

对于容器平台,Docker已经成了容器引擎公认的事实标准,而Kubernetes也是公认的容器编排最佳调度平台,早在一两年前声音还没有如此的一致,记得最初做容器平台技术调研时,还要关注Mesos,Swarm等,但现在无论是商业产品还是纯自研,基本都转向了Kubernetes的阵营,说到这里我来简单谈谈Docker和Kubernetes最吸引人,或者说最值得应用的地方。
##Docker
Docker我认为最有价值的地方是优秀的可移植性,我们日常中最常见的问题就是应用部署的环境差异,操作系统版本差异,生产测试环境不一致等,往往这种差异会导致应用部署调试消耗大量的人力,还容易出现安全隐患,即使很多部署工作已经自动化,但也对基础环境有一定要求,这点比虚拟机有了更友好的移植能力,Docker能够将应用程序和它依赖的操作系统、类库以及运行时环境整体打包,统一交付,真正抹去了应用程序多个运行实例间的环境和依赖版本差异,同时也把对运维人员的重度依赖解耦开来。Docker有一句响亮的口号不是白叫的“Build once,Run anywhere”。

还有一点就是快,秒级启动,虚拟机虽说是分钟级的启动,其实也已经非常快了,但从单体来看,当出现故障重启(Docker是重新生成),这种差别就是非常明显的,容器甚至可以做到外界无感知,尤其对于当需要应用扩容弹新的节点时,这种秒与分钟效率的差别,对应用的使用对象就是天壤之别的使用体验了,这里还不说虚拟机应急扩节点时应用配置的复杂度和配置时长,单是创建和启动时间,可能就是一个外部用户无感知,一个外部用户感觉到慢甚至短时间内服务不可用了。
1.jpg

##Kubernetes
Kubernetes,这个单词来自于希腊语,含义是舵手或领航员,简称K8S。2014年以Google内部用了很久的Brog为原型孵化出来贡献给开源社区的一个自动化部署、伸缩和编排容器的开源平台,其功能强大,Kubernetes实现了IP地址管理、负载均衡、服务发现和DNS名称注册,还原生提供运维人员更为关注的高可用特性及资源智能调度,在我看来Kubernetes对管理基础设施的抽象解耦比虚拟化技术更为彻底、极致,其对CPU兼容性、系统兼容性更为优秀。Kubernetes的自动扩缩容、负载均衡和重新启动应用程序的功能,这些也是使用IaaS或裸机时要二次定制开发的。

这里也要说下,Kubernetes不只支持Docker,它支持符合CRI(containerruntime interface)接口的容器引擎,CoreOS的rkt就是一大代表。

当然构成一套容器云平台,不只是Docker和Kubernetes就够了,他们只是应用运行和调度的最核心的载体,还要有一系列CI\CD、镜像管理、安全、监控、网络、存储等组件,才能构成一个完整的解决方案。
2.jpg

#计算方案
##应该部署在物理机还是虚拟机上?
这是架构设计时一定会讨论的一个问题,从容器云的架构设计来看,我觉得没有绝对的谁更好的答案,物理机或虚拟化平台均可以,前面也说到了,其核心组件K8S将基础设施抽象的更为极致,所以我认为要综合企业自身基础设施建设现状和内部制度流程等综合因素权衡。

如果之前已经有了IaaS平台建设,并且已经有成熟的运维规范和配套工具,那么就部署于IaaS之上,享受IaaS建设的红利。融入了自助服务、内部流程审批、应用软件安装等自动化流程及规范,且IaaS平台有一些对于运维人员爱不释手的功能——热迁移、快照、HA、快速创建虚拟机等,IaaS平台在易管理性和资源弹性上相比物理机还是优势巨大的。

如果没有现成的IaaS建设,那么我认为首选物理机,没有必要再去投入人力去设计IAAS基础设施,K8S原生解耦了基础设施的依赖,提供了智能调度和高可用能力,针对物理机去定制一些满足自身的管理功能和运维的自动化手段也是理想之选,毕竟建设一套适合自身企业需求的IAAS本身也是个巨大的工程,而且少了一层虚拟化,从架构来看更为清晰简洁,troubleshooting时理论故障点也会少些,虚拟化层的性能损耗也不用考虑了。
#网络方案
在容器平台的基础架构层面,网络方案的设计一般都是涉及讨论最多,意见碰撞最多的地方,毕竟网络是底层的基础设施,牵一发动全身,所以定会格外谨慎。underlay、overlay、routing的网络模型都比较了遍,当然这些方案都是要Kubernetes CNI(Container Network Interface)插件标准的,主要关注的有:ipvlan(underlay)、Macvlan(underlay)、Flannel(overlay)、Calico(routing)、NSX-T(routing)。

对于underlay的方案,对于传统的网络架构可以无缝适配,网络的管理模式(IP资源管理,流量管理等)也可以保持一致,但从Kubernetes的管理功能和生态圈来看,underlay的网络方案都不是方向,更多是一种适配传统网络架构的过渡方案,对于ip的管理还是要在外部完成,而且Kubernetes也失去了对于容器的隔离控制能力,此外,例如Macvlan要求启用混杂模式,ipvlan要求linux内核版本要在4.1之上等刚性要求,也不适合绝大多数企业的网络管理规范及操作系统使用现状。对于Overlay的方案,对于传统网络和容器通讯及跨Kubernetes集群的容器通讯需求,该类型方案均存在很大弊端,而且基于vxlan的数据封装传输,对于容器ip的流量监控也不易实现(除非支持解析 vxlan 数据包),对于vxlan的解封包,在性能上也会一定损失,性能表现亦不占优。所以,综合应用的通信需求、运维的管理需求、功能的完善度、技术趋势、性能、安全等多方面的因素,我认为routing的网络模型方案更优,routing模式对传统应用适配,接受传统组织结构都更友好。

接下来我也将routing方案的主要代表介绍下:
##Calico
Calico是综合对比后,各方面功能需求最为满足的开源网络方案,而且其在社区的活跃度非常高,通过调研了解到一些容器平台厂商也开始或已经在产品中集成此方案。

Calico是一个纯三层的数据中心网络方案(不需要Overlay),基于BGP协议通过路由转发的方式实现容器的跨主机通信。Calico 将每个节点(Node)虚拟为一个“路由器”,并为之分配独立的虚拟网段,该路由器为当前节点上的容器提供路由服务,完全利用路由规则实现动态组网,通过BGP协议通告路由,小规模部署可以直接互联,大规模下可通过指定的BGProutereflector来完成。

Calico在每一个计算节点实现了Linux内核级的vRouter来负责数据转发,所以性能优异。

其相较其他开源方案,还有一大优势是安全能力,基于iptables提供了丰富而灵活的network policy,支持很细致的ACL控制。

Calico主要由Felix、etcd、BGP client以及BGPRoute Reflector组成:

  1. Felix,Calico Agent,跑在每台需要运行Workload的节点上,主要负责为容器配置IP,路由及ACLs(iptable规则)等信息来确保Endpoint的连通状态;
  2. etcd,分布式键值存储,主要负责网络元数据一致性,确保Calico网络状态的准确性;
  3. BGP Client(BIRD),主要负责把Felix写入Kernel的路由信息分发(广播)到当前Calico网络( 通过BGP协议),确保Workload间的通信的有效性;
  4. BGP Route Reflector(BIRD),大规模集群的分级路由分发,摒弃所有节点互联的Mesh模式,通过一个或者多个BGP Route Reflector来完成集中式的路由分发。

3.jpg

#NSX-T
NSX-T是VMware的企业级网络虚拟化商业解决方案,是2016年5月在NSX-V/NSX-MH之后推出的新产品,到现在是2.3版本,T版本主要是加入容器网络支持和兼容裸金属物理机这两个重要功能,未来也会逐步将NSX-V/NSX-MH的代码整合完成替代。同Calico一样,也是内核级的数据转发,性能优异,而且产品自身提供了良好的负载均衡及安全能力。对于商业软件,企业级的服务支持和可视化的管理界面是商业方案优势较为明显的地方。

NSX-T我认为最显著的特点是,与传统网络有个南北向清晰地边界——Edge节点,而东西向全部在自己的自制范围内。

这里还有两个我认为比较重要的点应该了解一下,NSX-T与K8S的适配方式,是通过NSX-T Container Plug-in(NCP)插件与其集成,包括其他的容器平台解决方案也是通过此插件进行集成,例如Pivotal Cloud Foundry等。

还有一点是从NSX-T开始,VMware已经从NSX-V使用的基于vxlan的封装转移,并采用了更新的“Geneve”封装。Geneve是由VMware,Microsoft,Red Hat和Intel共同撰写的新版封装。Geneve将当前最佳的封装协议(如VXLAN,STT和NVGRE)整合到一个协议中。封装头的MTU为1600,所以对NSX-T自治域内的MTU值要求大于1600。
4.jpg

#存储方案
存储也是基础架构的重要一环,尤其是对于有状态的应用,数据持久化需求的支持。

我觉得在Kubernetes里,对于基础资源(CPU、内存、网络、存储)的管理,存储使用的设计较为别致,使用方式有别于常见的思路,还需要认真去理解。

这里简单介绍下在K8S中存储设计的四个重要概念:Volume、PV(PersistentVolume)、PVC(PersistentVolumeClaim)、Storage Class。
##Volume
Volume是最基础的存储抽象,其支持多种类型,包括本地存储、NFS、FC以及众多的云存储,也可以通过flex volume driver或CSI(contaioner storage Interface)编写自己的存储插件来支持特定的存储系统。

Volume可以被Pod直接使用,也可以被PV使用。普通的Volume和Pod之间是一种静态的绑定关系,在定义Pod的同时,通过volume属性来定义存储的类型,通过volumeMount来定义容器内的挂载点。

Volume有一个重要属性是,它与所属的Pod具有相同的生命周期。

Pod是Kubernetes的最小工作单元,每个 Pod 包含一个或多个容器
##PV
PV与普通的Volume不同,它是Kubernetes中的一个资源对象,创建一个PV相当于创建了一个存储资源对象,向用户屏蔽了具体的存储实现形式,而且这个资源的使用要通过PVC来请求。PV定义的内容包含存储的类型,存储的大小和访问模式等。

PV的生命周期独立于Pod,即当使用它的Pod销毁时对PV没有影响。
##PVC
PVC是用户对存储资源PV的请求,请求信息包含存储大小,访问模式等。根据PVC中指定的条件Kubernetes寻找系统中的PV资源并进行绑定。PVC消耗PV资源,PV和PVC是一一对应的。
##StorageClass
StorageClass的引入是为了解决个性化的存储需求动态供给的问题,集群管理员可以先将存储资源定义为不同类型的资源,比如高性能存储、常规存储等,之后通过StorageClass的定义动态的去创建PV,然后通过PVC来请求PV绑定。

对于多数银行业的企业,都有丰富的SAN和NAS的存储管理及运维经验,结合应用的存储需求、平台镜像方案的设计,以及银行业的应用系统普遍有多中心部署的监管需求,我认为采用NFS类型的存储支持容器数据持久化以及镜像服务的相关存储需求对容器平台在银行业的落地不失为一个不错的选择,此外还应同时开展对象存储的研究与测试,以给应用对数据存储的使用方式更多选择。

容器云平台建设会是一个不断完善、迭代积累的过程,同时容器相关技术的发展变化非常之快,在平台的建设中要持续保持新技术的敏锐嗅觉,来提升应用服务、运维管理、安全监管等各方面的水平。

原文链接:https://mp.weixin.qq.com/s/O6Za5JGywmnZ-Rswlu5WzA

Docker容器互联方法

翔宇 发表了文章 • 0 个评论 • 316 次浏览 • 2019-05-28 22:34 • 来自相关话题

【编者的话】本文为Eddy Mavungu博士于DEIS官方博客中发布的系列文章的第二部分,Eddy博士在本篇系列文章中分享了Docker容器间互联的方法,并且做了演示。Eddy博士是DEIS公司的创始人,同时也是一位高级研究顾问。本文根据他于DEIS官方博客 ...查看全部
【编者的话】本文为Eddy Mavungu博士于DEIS官方博客中发布的系列文章的第二部分,Eddy博士在本篇系列文章中分享了Docker容器间互联的方法,并且做了演示。Eddy博士是DEIS公司的创始人,同时也是一位高级研究顾问。本文根据他于DEIS官方博客上发布的文章翻译而成。

这篇系列文章的第二部分会看一下如何连接Docker各容器。

我们在第一节谈及了Docker的bridge接口,它可以让我们连接所有在相同Docker宿主机上的容器。特别需要指出的是,我们看了三个基本并且比较早先的网络驱动技术:端口公开(port exposure),端口绑定(port binding)以及链接(linking)。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

在本文,我们将来看这些更高级并且最新的基于bridge接口的使用案例。

同时,我们也会看看使用overlay技术来将不同宿主机上的Docker容器连接起来。
#用户定义的网络(User-Defined Networks)
Docker公司在2015年11月初发布了Docker 1.9.0,随之而来整合了一些令人兴奋的关于网络方面的新内容。通过这些更新,如果我们现在为了让两个容器间可以相互通信,那么只需要将他们放在同一网络或者子网中。

让我们来证明一次。

首先,来看看我们准备了哪些:
$ sudo docker network ls
NETWORK ID NAME DRIVER
362c9d3713cc bridge bridge
fbd276b0df0a singlehost bridge
591d6ac8b537 none null
ac7971601441 host host

现在,让我们来创建网络:
$ sudo docker network create backend

如果起作用了,我们的网络列表上会显示刚才新建立的网络:
$ sudo docker network ls
NETWORK ID NAME DRIVER
362c9d3713cc bridge bridge
fbd276b0df0a singlehost bridge
591d6ac8b537 none null
ac7971601441 host host
d97889cef288 backend bridge

我们可以看到在这里backend已经被bridge驱动创建出来了。这是一个桥接(bridge)网络,就像在前文的第一部分中提到的一样,它也同样适用于所有在该宿主机上的容器。

我们将会用到上一节中创建的client_img和server_img镜像。如果你还没有在你的主机上配置好他们,可以现在查阅第一节并且设置好,这并不会花费太长时间。

什么?你说你已经设置好了?太棒了。

让我们从server_img镜像中运行一个服务器容器并且通过--net选项把它放置于之前配置的backend网络中。

就像下面命令一样:
$ sudo docker run -itd --net=backend --name=server server_img /bin/bash

像之前一样,登录到该容器:
$ sudo docker attach server

如果你看不见shell提示符,按键盘方向键的上箭头。

现在让我们启动在该容器里基于Apache的HTTP服务:
$ /etc/init.d/apache2 start

到这里为止,任何位于backend网络中的容器将可以连接到我们刚才创建的Apache HTTP服务器上。

我们可以通过在另一个终端上开启一个客户端容器并且把它至于backend网络进行测试。

就像这样:
$ sudo docker run -itd --net=backend --name=client client_img /bin/bash

进入容器:
$ sudo docker attach client

同样地,如果你看不见shell提示符,按键盘方向键的上箭头。
接着运行:
$ curl server

你应该可以看见默认的HTML页面。这意味着我们配置的网络是可以正常运行的。

就像在本系列文章的第一节中提及到的一样,Docker负责设置容器名称便于解析,这就是为什么我们可以直接用curl server 这个命令而不用知晓它的IP地址。

我们可以创建多个用户定义的网络,并且可以根据应用程序的拓扑结构把这些容器放在单个或多个网络中。这是非常灵活的,特别是对于那些需要交付微服务(microservices),多租户(multitenancy)及微分段(micro-segmentation)的人员来说是非常有用的。
#多主机网络(Multi-Host Networking)
如果我们想要建立一个跨越多台主机的网络该怎么做呢?当然,自从Docker1.9.0之后,你就可以这么做了!

目前为止,我们已经使用了基于本地范围的bridge网络驱动,这意味着桥接网络是在Docker宿主机本地的。Docker现在提供了一个新的全局范围的overlay网络驱动,这意味着overlay的网络可以跨越多台Docker宿主机。并且这些Docker宿主机可以存在于不同的数据中心,甚至不同的云服务提供商中!

为了设置一个overlay网络,以下几点是必须的:

* 一台内核版本高于3.16的主机
* 关键的key-value存储(例如 etcd, ConsulApache ZooKeeper
* 集群内的主机可以保证连接到以上的key-value存储
* 每一个集群内的主机都正确配置了基于 Docker Engine的后台实例

让我们来看一个案例吧。

我将会使用multihost-local.sh脚本和Docker Machine这个命令去开启三台虚拟主机。

这个脚本是用于虚拟机的而不是用于容器的。在这之后,我们在这些虚拟机上面运行Docker命令去模拟一个Docker宿主机集群。

在运行过脚本之后,可以看到我现在有的主机清单:
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM ERRORS
mhl-consul - virtualbox Running tcp://192.168.99.100:2376
mhl-demo0 - virtualbox Running tcp://192.168.99.101:2376
mhl-demo1 - virtualbox Running tcp://192.168.99.102:2376

好了,那么让我们倒退回去看下刚才发生了些什么。

这个脚本利用了Docker Machine,这意味着你必须安装它。在本篇文章中,我们安装了0.5.2版本的Docker Machine。你可以参考该版本的发行注释说明来下载并安装它。

该脚本(multihost-local.sh)使用了Docker Machine去部署三台基于VirtualBox的虚拟机并且安装并适当的配置了Docker Engine。

Docker Machine可以与很多主流虚拟化管理程序以及云服务提供商一起协助。它现在支持AWS、Digital Ocean、Google Cloud Platform、IBM Softlayer、Microsoft Azure和Hyper-V、OpenStack、Rachspace、VitrualBox、VMware Fusion®、vCloud® Air™ and vSphere®。

我们现在有了三个虚拟机:

* mhl-consul: 运行 Consul
* mhl-demo0: Docker 集群节点
* mhl-demo1: Docker 集群节点

这些Docker集群节点是被配置为协调通过VM来运行Consul,我们的key-value仓库。这也就是这些集群所涉及到的。

很酷吧,让我们再快速地往前看一看。

现在,让我们设置一个overlay网络。

首先,我们需要用一个在mhl-demo0这个VM上的终端,像这样:
$ eval $(docker-machine env mhl-demo0)

然后运行:
$ docker network create -d overlay myapp

该命令建立了一个名为myapp的跨越所有集群中主机的overlay网络。由于Docker通过key-value与该集群上的其他主机协调使得该操作变成了可能。

为了确保它正常的工作,我们可以通过终端登录到每一个集群中的VM去列出这些Docker网络。

复制下面所有的eval命令,然后用相对应的主机名去替换mhl-demo0。

然后运行:
$ docker network ls
NETWORK ID NAME DRIVER
7b9e349b2f01 host host
1f6a49cf5d40 bridge bridge
38e2eba8fbc8 none null
385a8bd92085 myapp overlay

到这里你就看到名为myapp的overlay网络了。

咱们成功啦!

但请记住:我们目前仅仅是创建了一个基于Docker 虚拟机的集群并配置了用于共享的overlay网络。我们实际上并没有创建任何Docker容器。所以让我们接着创建它们并来试一下这个overlay网络。

我们将做以下步骤:

1. 在mhl-demo0宿主机上运行默认地nginx镜像(这提供给了我们一个预先配置好的Nginx HTTP服务器)
2. 在mhl-demo1宿主机上运行默认的busybox镜像(它提供给我们一个基本的操作系统并且还包含一个类似于基于GNU的Wget工具)
3. 将两个容器添加进myapp网络
4. 测试他们之间的网络连通性

首先,让我们调出在mhl-demo0宿主机上的终端:
$ eval $(docker-machine env mhl-demo0)

然后启动nginx镜像:
$ docker run --name ng1 --net=myapp -d nginx

简而言之,我们现在有以下环境:

* 一台基于Nginx的HTTP服务器
* 该服务跑在名为ng1的容器里
* 它处在myapp网络中
* 该容器位于mhl-demo0宿主机上

让我们从其他宿主机上的另外一个容器试着去访问该环境,去验证它是可用的。

这一次调出mhl-demo1宿主机上的终端:
$ eval $(docker-machine env mhl-demo1)

然后运行:
$ docker run -it --net=myapp busybox wget -qO- ng1

以上命令其实做了以下几点:

* 从busybox镜像中创建了一个未命名的容器
* 将它添加进了myapp网络
* 运行了wget -qO- ng1命令
* 并且停止了该容器(我们在这之前是让容器运行的)

在以上的Wget命令中,ng1是我们Nginx的容器名称。Docker可以让我们使用解析到的主机名作为容器名称,甚至该容器是运行在不同的Docker宿主机上。

如果所有操作都是成功的,那么我们应该会看见以下类似的内容:






Welcome to nginx!

你看吧!我们现在拥有了一个基于多主机的容器网络。
#总结
Docker给予了诸如轻量级且独立地并能隔离的环境这样的优点。然而,要使容器对我们而言有用途,容器之间以及容器与主机网络之间要能互相通信才是至关重要的。

在这一系列文章中,我们探索了一些容器间本地互联和跨多个宿主机互联的方法。同样地,我们也聊了聊该如何在主机网络中去连接多个容器。

原文链接:Connecting Docker Containers, Part Two (翻译:薛开成)

===========================================
译者介绍
薛开成,趋势科技南京研发中心工程工具服务事业部基础架构高级工程师,负责容器仓库实施及落地。

使用Spring Cloud和Docker构建微服务架构

老马 发表了文章 • 0 个评论 • 240 次浏览 • 2019-05-28 22:27 • 来自相关话题

【编者的话】如何使用Spring Boot、Spring Cloud、Docker和Netflix的一些开源工具来构建一个微服务架构。本文通过使用Spring Boot、Spring Cloud和Docker构建的概念型应用示例,提供了了解常见的微服务架构模式 ...查看全部
【编者的话】如何使用Spring Boot、Spring Cloud、Docker和Netflix的一些开源工具来构建一个微服务架构。本文通过使用Spring Boot、Spring Cloud和Docker构建的概念型应用示例,提供了了解常见的微服务架构模式的起点。

该代码可以在GitHub上获得,并且在Docker Hub上提供了镜像。您只需要一个命令即可启动整个系统。

我选择了一个老项目作为这个系统的基础,它的后端以前是单一应用。此应用提供了处理个人财务、整理收入开销、管理储蓄、分析统计和创建简单预测等功能。如果你想和更多Spring Cloud技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
#功能服务
整个应用分解为三个核心微服务。它们都是可以独立部署的应用,围绕着某些业务功能进行组织。
1.png


账户服务

包含一般用户输入逻辑和验证:收入/开销记录、储蓄和账户设置。
2.png


统计服务

计算主要的统计参数,并捕获每一个账户的时间序列。数据点包含基于货币和时间段正常化后的值。该数据可用于跟踪账户生命周期中的现金流量动态。
3.png


通知服务

存储用户的联系信息和通知设置(如提醒和备份频率)。安排工作人员从其它服务收集所需的信息并向订阅的客户发送电子邮件。
4.png


注意

* 每一个微服务拥有自己的数据库,因此没有办法绕过API直接访问持久数据。
* 在这个项目中,我使用MongoDB作为每一个服务的主数据库。拥有一个多种类持久化架构(polyglot persistence architecture)也是很有意义的。
* 服务间(Service-to-service)通信是非常简单的:微服务仅使用同步的REST API进行通信。现实中的系统的常见做法是使用互动风格的组合。例如,执行同步的GET请求检索数据,并通过消息代理(broker)使用异步方法执行创建/更新操作,以便解除服务和缓冲消息之间的耦合。然而,这带给我们是最终的一致性

#基础设施服务
分布式系统中常见的模式,可以帮助我们描述核心服务是怎样工作的。Spring Cloud提供了强大的工具,可以增强Spring Boot应用的行为来实现这些模式。我会简要介绍一下:
5.png

##配置服务
Spring Cloud Config是分布式系统的水平扩展集中式配置服务。它使用了当前支持的本地存储、Git和Subversion等可拔插存储库层(repository layer)。

在此项目中,我使用了native profile,它简单地从本地classpath下加载配置文件。您可以在配置服务资源中查看shared目录。现在,当通知服务请求它的配置时,配置服务将响应回shared/notification-service.yml和shared/application.yml(所有客户端应用之间共享)。

客户端使用

只需要使用sprng-cloud-starter-config依赖构建Spring Boot应用,自动配置将会完成其它工作。

现在您的应用中不需要任何嵌入的properties,只需要提供有应用名称和配置服务url的bootstrap.yml即可:
spring:
application:
name: notification-service
cloud:
config:
uri: http://config:8888
fail-fast: true

使用Spring Cloud Config,您可以动态更改应用配置

比如,EmailService bean使用了@RefreshScope注解。这意味着您可以更改电子邮件的内容和主题,而无需重新构建和重启通知服务应用。

首先,在配置服务器中更改必要的属性。然后,对通知服务执行刷新请求:curl -H "Authorization: Bearer #token#" -XPOST http://127.0.0.1:8000/notifications/refresh。

您也可以使用webhook来自动执行此过程

注意

* 动态刷新存在一些限制。@RefreshScope不能和@Configuraion类一同工作,并且不会作用于@Scheduled方法。
* fail-fast属性意味着如果Spring Boot应用无法连接到配置服务,将会立即启动失败。当您一起启动所有应用时,这非常有用。
* 下面有重要的安全提示

##授权服务
负责授权的部分被完全提取到单独的服务器,它为后端资源服务提供OAuth2令牌。授权服务器用于用户授权以及在周边内进行安全的机器间通信。

在此项目中,我使用密码凭据作为用户授权的授权类型(因为它仅由本地应用UI使用)和客户端凭据作为微服务授权的授权类型。

Spring Cloud Security提供了方便的注解和自动配置,使其在服务器端或者客户端都可以很容易地实现。您可以在文档中了解到更多信息,并在授权服务器代码中检查配置明细。

从客户端来看,一切都与传统的基于会话的授权完全相同。您可以从请求中检索Principal对象、检查用户角色和其它基于表达式访问控制和@PreAuthorize注解的内容。

PiggyMetrics(帐户服务、统计服务、通知服务和浏览器)中的每一个客户端都有一个范围:用于后台服务的服务器、用于浏览器展示的UI。所以我们也可以保护控制器避免受到外部访问,例如:
@PreAuthorize("#oauth2.hasScope('server')")
@RequestMapping(value = "accounts/{name}", method = RequestMethod.GET)
public List getStatisticsByAccountName(@PathVariable String name) {
return statisticsService.findByAccountName(name);
}

##API网关
您可以看到,有三个核心服务。它们向客户端暴露外部API。在现实系统中,这个数量可以非常快速地增长,同时整个系统将变得非常复杂。实际上,一个复杂页面的渲染可能涉及到数百个服务。

理论上,客户端可以直接向每个微服务直接发送请求。但是这种方式是存在挑战和限制的,如果需要知道所有端点的地址,分别对每一段信息执行http请求,将结果合并到客户端。另一个问题是,这不是web友好协议,可能只在后端使用。

通常一个更好的方法是使用API网关。它是系统的单个入口点,用于通过将请求路由到适当的后端服务或者通过调用多个后端服务并聚合结果来处理请求。此外,它还可以用于认证、insights、压力测试、金丝雀测试(canary testing)、服务迁移、静态响应处理和主动变换管理。

Netflix开源这样的边缘服务,现在用Spring Cloud,我们可以用一个@EnabledZuulProxy注解来启用它。在这个项目中,我使用Zuul存储静态内容(UI应用),并将请求路由到适当的微服务。以下是一个简单的基于前缀(prefix-based)路由的通知服务配置:
zuul:
routes:
notification-service:
path: /notifications/**
serviceId: notification-service
stripPrefix: false

这意味着所有以/notification开头的请求将被路由到通知服务。您可以看到,里面没有硬编码的地址。Zuul使用服务发现机制来定位通知服务实例以及断路器和负载均衡器,如下所述。
##服务发现
另一种常见的架构模式是服务发现。它允许自动检测服务实例的网络位置,由于自动扩展、故障和升级,它可能会动态分配地址。

服务发现的关键部分是注册。我使用Netflix Eureka进行这个项目,当客户端需要负责确定可以用的服务实例(使用注册服务器)的位置和跨平台的负载均衡请求时,Eureka就是客户端发现模式的一个很好的例子。

使用Spring Boot,您可以使用spring-cloud-starter-eureka-server依赖、@EnabledEurekaServer注解和简单的配置属性轻松构建Eureka注册中心(Eureka Registry)。

使用@EnabledDiscoveryClient注解和带有应用名称的bootstrap.yml来启用客户端支持:
spring:
application:
name: notification-service

现在,在应用启动时,它将向Eureka服务器注册并提供元数据,如主机和端口、健康指示器URL、主页等。Eureka接收来自从属于某服务的每个实例的心跳消息。如果心跳失败超过配置的时间表,该实例将从注册表中删除。

此外,Eureka还提供了一个简单的界面,您可以通过它来跟踪运行中的服务和可用实例的数量:http://localhost:8761
6.png

##负载均衡器、断路器和Http客户端
Netflix OSS提供了另一套很棒的工具。

Ribbon

Ribbon是一个客户端负载均衡器,可以很好地控制HTTP和TCP客户端的行为。与传统的负载均衡器相比,每次线上调用都不需要额外的跳跃——您可以直接联系所需的服务。

它与Spring Cloud和服务发现是集成在一起的,可开箱即用。Eureka客户端提供了可用服务器的动态列表,因此Ribbon可以在它们之间进行平衡。

Hystrix

Hystrix是断路器模式的一种实现,它可以通过网络访问依赖来控制延迟和故障。中心思想是在具有大量微服务的分布式环境中停止级联故障。这有助于快速失败并尽快恢复——自我修复在容错系统中是非常重要的。

除了断路器控制,在使用Hystrix,您可以添加一个备用方法,在主命令失败的情况下,该方法将被调用以获取默认值。

此外,Hystrix生成每个命令的执行结果和延迟的度量,我们可以用它来监视系统的行为

Feign

Feign是一个声明式HTTP客户端,能与Ribbon和Hystrix无缝集成。实际上,通过一个spring-cloud-starter-feign依赖和@EnabledFeignClients注解,您可以使用一整套负载均衡器、断路器和HTTP客户端,并附带一个合理的的默认配置。

以下是账户服务的示例:
@FeignClient(name = "statistics-service")
public interface StatisticsServiceClient {
@RequestMapping(method = RequestMethod.PUT, value = "/statistics/{accountName}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
void updateStatistics(@PathVariable("accountName") String accountName, Account account);
}


* 您需要的只是一个接口
* 您可以在Spring MVC控制器和Feign方法之间共享@RequestMapping部分
* 以上示例仅指定所需要的服务ID——statistics-service,这得益于Eureka的自动发现(但显然您可以使用特定的URL访问任何资源)。

##监控仪表盘
在这个项目配置中,Hystrix的每一个微服务都通过Spring Cloud Bus(通过AMQP broker)将指标推送到Turbine。监控项目只是一个使用了TurbineHystrix仪表盘的小型Spring Boot应用。

让我们看看系统行为在负载下:账户服务调用统计服务和它在一个变化的模拟延迟下的响应。响应超时阈值设置为1秒。
7.png

##日志分析
集中式日志记录在尝试查找分布式环境中的问题时非常有用。Elasticsearch、Logstash和Kibana技术栈可让您轻松搜索和分析您的日志、利用率和网络活动数据。在我的另一个项目中已经有现成的Docker配置。
##安全

高级安全配置已经超过了此概念性项目的范围。为了更真实地模拟真实系统,请考虑使用https和JCE密钥库来加密微服务密码和配置服务器的properties内容(有关详细信息,请参阅文档)。
#基础设施自动化

部署微服务比部署单一的应用的流程要复杂得多,因为它们相互依赖。拥有完全基础设置自动化是非常重要的。我们可以通过持续交付的方式获得以下好处:

* 随时发布软件的能力。
* 任何构建都可能最终成为一个发行版本。
* 构建工件(artifact)一次,根据需要进行部署。

这是一个简单的持续交付工作流程,在这个项目的实现:

在此配置中,Travis CI为每一个成功的Git推送创建了标记镜像。因此,每一个微服务在Docker Hub上的都会有一个latest镜像,而较旧的镜像则使用Git提交的哈希进行标记。如果有需要,可以轻松部署任何一个,并快速回滚。
8.png

#如何运行全部?
这真的很简单,我建议您尝试一下。请记住,您将要启动8个Spring Boot应用、4个MongoDB实例和RabbitMq。确保您的机器上有4GB的内存。您可以随时通过网关、注册中心、配置、认证服务和账户中心运行重要的服务。

运行之前

* 安装Docker和Docker Compose。
* 配置环境变量:CONFIG_SERVICE_PASSWORD, NOTIFICATION_SERVICE_PASSWORD, STATISTICS_SERVICE_PASSWORD, ACCOUNT_SERVICE_PASSWORD, MONGODB_PASSWORD

生产模式

在这种模式下,所有最新的镜像都将从Docker Hub上拉取。只需要复制docker-compose.yml并执行docker-compose up -d即可。

开发模式

如果您想自己构建镜像(例如,在代码中进行一些修改),您需要克隆所有仓库(repository)并使用Mavne构建工件(artifact)。然后,运行docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d

docker-compose.dev.yml继承了docker-compose.yml,附带额外配置,可在本地构建镜像,并暴露所有容器端口以方便开发。

重要的端点(Endpoint)

* localhost:80 —— 网关
* localhost:8761 —— Eureka仪表盘
* localhost:9000 —— Hystrix仪表盘
* localhost:8989 —— Turbine stream(Hystrix仪表盘来源)
* localhost:15672 —— RabbitMq管理

注意

所有Spring Boot应用都需要运行配置服务器才能启动。得益于Spring Boot的fail-fast属性和docker-compsoe的restart:always选项,我们可以同时启动所有容器。这意味着所有依赖的容器将尝试重新启动,直到配置服务器启动运行为止。

此外,服务发现机制在所有应用启动后需要一段时间。在实例、Eureka服务器和客户端在其本地缓存中都具有相同的元数据之前,任何服务都不可用于客户端发现,因此可能需要3次心跳。默认的心跳周期为30秒。

原文链接:Microservice Architectures With Spring Cloud and Docker(翻译:Oopsguy

关于swarm网络不能访问的问题

回复

ighack 发起了问题 • 1 人关注 • 0 个回复 • 8 次浏览 • 2019-06-19 11:57 • 来自相关话题

docker挂载mysql5.7日志无效

回复

styshoo 回复了问题 • 2 人关注 • 1 个回复 • 99 次浏览 • 2019-06-19 08:22 • 来自相关话题

docker中文文档挂了?

回复

outyua 发起了问题 • 1 人关注 • 0 个回复 • 424 次浏览 • 2019-05-20 18:12 • 来自相关话题

pod在mount到宿主机的时候如何路径上带上podname

回复

skyeydemon 回复了问题 • 2 人关注 • 1 个回复 • 648 次浏览 • 2019-05-06 16:39 • 来自相关话题

为什么Kubernetes Service的负载均衡会有模拟丢包率的IPtables条目?

回复

徐新坤 回复了问题 • 3 人关注 • 3 个回复 • 3446 次浏览 • 2019-03-26 09:29 • 来自相关话题

创建了一个有ssh服务的容器,如何ssh登录后,可以获取到Dockerfile中的环境变量呢?

回复

徐新坤 回复了问题 • 2 人关注 • 1 个回复 • 2214 次浏览 • 2019-03-14 17:42 • 来自相关话题

dockerhub organization 如何才能访问 github organization中的仓库,来 auto build

回复

20% 发起了问题 • 1 人关注 • 0 个回复 • 677 次浏览 • 2019-03-06 22:51 • 来自相关话题

Docker网络桥接问题

回复

Leli 回复了问题 • 6 人关注 • 6 个回复 • 3780 次浏览 • 2019-01-25 10:10 • 来自相关话题

容器中删除文件的问题

回复

wx65251206 回复了问题 • 3 人关注 • 5 个回复 • 5648 次浏览 • 2018-12-17 14:58 • 来自相关话题

求最新docker/swarm源码中远程包的地址

回复

Macallan 发起了问题 • 1 人关注 • 0 个回复 • 821 次浏览 • 2018-11-02 19:37 • 来自相关话题

在Docker上编译OpenJDK 8

李颖杰 发表了文章 • 0 个评论 • 136 次浏览 • 2019-06-18 11:06 • 来自相关话题

以前曾经试过在VMware上安装Linux,但是每次都不顺利,例如Linux环境,预装依赖软件,OpenJDK源码的选择等环境都会遇到问题,一旦失败再重新开始挺费时间的,现在用Docker就省事多了,镜像和容器的创建和删除都比较简单干净,专心做好Dock ...查看全部

以前曾经试过在VMware上安装Linux,但是每次都不顺利,例如Linux环境,预装依赖软件,OpenJDK源码的选择等环境都会遇到问题,一旦失败再重新开始挺费时间的,现在用Docker就省事多了,镜像和容器的创建和删除都比较简单干净,专心做好Dockerfile就行,下面我们一起来实践一下吧。



本次实战用到的所有文件,已经打包到GitHub上,路径是git@github.com:zq2599/centos7_build_openjdk8.git,欢迎您来使用(git clone git@github.com:zq2599/centos7_build_openjdk8.git)。



本次编译实战的基本步骤如下:


编写的Dockerfile中要做的如下的事情:




  1. 安装依赖的软件;

  2. 把OpenJDK的源码复制到镜像中;



在编写Dockerfile之前要做三个重要的选择,如下:




  1. Linux:我选择了CentOS 7,之前试过Ubuntu 16.04,但是在Configure的时候提示”freetype”没有安装,我按照提示去装了,再次Configure的时候继续提示”freetype”没有安装……(此问题现在还没解决,如果您解决过相同问题,请您告诉一下解决方法,谢谢了!)

  2. OpenJDK源码,这次要编译的是OpenJDK 8,源码的下载地址在这里

  3. Bootstrap JDK:即编译时要用到的JDK,下载了OpenJDK的源码后,解压开可以看到“README-builds.html”这个文件,里面有对Bootstrap JDK的描述:




看得出,需要安装JDK 7来编译OpenJDK 8的源码。



OK,关键问题都已确认,即将开始Dockerfile制作,不过制作之前还有个小问题需要先想好:本次我打算把制作镜像所需的Dockerfile和依赖文件都放到GitHub上去,这样做的好处有两个:




  1. 读者们从Git上clone下来之后直接执行Docker build就能在本地构建镜像;

  2. daocloud.io网站上支持通过执行GitHub目录的方式在线构建镜像,后面我们会实践在daocloud.io上构建镜像并部署到腾讯云或者阿里云服务器上。



上传到Gith=Hub时,除了Dockerfile,还要上传的文件有两个:JDK 1.7和OpenJDK 8源码,都超过了100M,如下图:


这就麻烦了,GitHub上传文件的时候,单个文件不能超过100M,否则push的时候会被服务器拒绝,解决这个问题有两个办法:



1、构建镜像的时候不要把这两个文件复制到镜像中了,改为在Dockerfile中通过执行wget命令将这两个文件分别下载到镜像中,OpenJDK的下载路径是:http://www.java.net/download/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip,而OpenJDK 1.7的下载路径就难办了,Oracle上下载历史版本的时候,是要做登录操作的,这个在Dockerfile中难以实现,找到了一个下载地址:https://mirror.its.sfu.ca/mirror/CentOS-Third-Party/NSG/common/x86_64/jdk-7u80-linux-x64.rpm,但是下载速度很慢,最少一个小时以上了,所以wget这种方法看似简单,但下载文件耗时实在太长;



2、第二种方法比较简单易用,就是在Mac或者Linux上先用split命令将文件分割成多个,再上传到GitHub上,在Dockerfile中有对应的命令将分割后的文件恢复成分割前的原文件,具体的分割命令如下:





split -b 50m jdk-7u71-linux-x64.rpm jdkrpm-


这个命令是将jdk-7u71-linux-x64.rpm分割成不超过50m的多个文件,分割后的文件以jdkrpm-作为文件名的前缀,如下图:


OpenJDK的源码用如下命令分割:





split -b 50m openjdk-8-src-b132-03_mar_2014.zip openjdksrc-


Dockerfile中,从分割文件恢复以上两个原文件的命令为:





cat jdkrpm-* > jdk-7u71-linux-x64.rpm
cat openjdksrc-* > openjdk-8-src-b132-03_mar_2014.zip


以上就是两种处理大文件的方法,本文用的是第二种,即先分割上传到Git,在Dockerfile中将已分割文件恢复成原文件再使用。



好了,前期的准备工作已经做完了,现在可以编写Dockerfile文件了,整个文件中要做的事情列出如下:



1、安装依赖软件,例如libXtst-devel,libXt-devel等等,这些都是编译前的Configure命令要检查的,检查不过无法进行编译;



2、把分割后的OpenJDK源码复制到镜像文件中,再合成,再解压;



3、安装JDK 7,把分割后的安装文件复制到镜像中合成,然后安装;



4、为了方便用户进入容器后快速开始编译,我们做了一个shell脚本start_make.sh,把这个脚本也要复制到镜像中,内容如下:





#!/bin/bash
$WORK_PATH/$OPENJDK_SRC_DIR/configure
echo "start make"
cd $WORK_PATH/$OPENJDK_SRC_DIR
make all ZIP_DEBUGINFO_FILES=0 DISABLE_HOTSPOT_OS_VERSION_CHECK=OK


5、清理无用的文件,例如OpenJDK源码的压缩文件,JDK 7的安装文件等;



按照以上步骤,最终写出的Dockerfile文件如下:





# Docker image of compile and build enviroment for openjdk8
# VERSION 0.0.1
# Author: bolingcavalry

#基础镜像使用CentOS 7
FROM centos:centos7

#作者
MAINTAINER BolingCavalry

#定义工作目录
ENV WORK_PATH /usr/local

#定义JDK 1.7的文件名
ENV JDK_RPM_FILE jdk-7u71-linux-x64.rpm

#定义OpenJDK源码的文件名
ENV OPENJDK_SRC_ZIP openjdk-8-src-b132-03_mar_2014.zip

#定义解压缩后的文件名
ENV OPENJDK_SRC_DIR openjdk

#yum更新
RUN yum -y update

#安装工具集
RUN yum -y groupinstall "Development Tools"

#安装即将用到的软件
RUN yum -y install unzip libXtst-devel libXt-devel libXrender-devel cups-devel freetype-devel alsa-lib-devel which

#把分割过的JDK 1.7安装文件复制到工作目录
COPY ./jdkrpm-* $WORK_PATH/

#用本地分割过的文件恢复原有的JDK 1.7的安装文件
RUN cat $WORK_PATH/jdkrpm-* > $WORK_PATH/$JDK_RPM_FILE

#本地安装JDK 1.7
RUN yum -y localinstall $WORK_PATH/$JDK_RPM_FILE

#把分割过的OpenJDK 8的源码压缩包复制到工作目录
COPY ./openjdksrc-* $WORK_PATH/

#用本地分割过的文件恢复原有的OpenJDK 8的源码压缩包
RUN cat $WORK_PATH/openjdksrc-* > $WORK_PATH/$OPENJDK_SRC_ZIP

#解压缩源码
RUN unzip $WORK_PATH/$OPENJDK_SRC_ZIP -d $WORK_PATH

#复制启动编译的shell
COPY ./start_make.sh $WORK_PATH/$OPENJDK_SRC_DIR/

#给执行文件增加可执行权限:Configure文件
RUN chmod a+x $WORK_PATH/$OPENJDK_SRC_DIR/configure

#给执行文件增加可执行权限:启动编译文件
RUN chmod a+x $WORK_PATH/$OPENJDK_SRC_DIR/start_make.sh

#删除分割文件
RUN rm $WORK_PATH/jdkrpm-*

#删除分割文件
RUN rm $WORK_PATH/openjdksrc-*

#删除JDK安装包文件
RUN rm $WORK_PATH/$JDK_RPM_FILE

#删除OpenJDK源码压缩文件
RUN rm $WORK_PATH/$OPENJDK_SRC_ZIP


至此,镜像文件制作所需的材料都已经齐全了,如下图:


现在让我们开始制作镜像吧,打开终端,进入Dockerfile所在目录,执行命令:





docker build -t bolingcavalryopenjdk:0.0.1 .


因为要在线安装不少的软件,所以可能耗时会略长,和网络带宽有关,我在住处用家庭网络大概10分钟左右构建成功,执行目录docker images查看镜像,新的镜像文件已经生成了,如下图:


现在启动一个容器试试吧:





docker run --name=jdk001 -idt bolingcavalryopenjdk:0.0.1


容器已经启动,再执行以下命令进入容器:





docker exec -it jdk001 /bin/bash


进去后,直接到/usr/local/openjdk目录下,执行./start_make.sh,开始编译了,整个过程的耗时和当前电脑的硬件配置有关,我用i7处理器的Mac Pro 15大约要用20多分钟,编译结束后会有类似下图的输出:


这时候去/usr/local/openjdk目录下看看,发现多了一个build目录,这里面就是编译好的结果,如下图:


build目录下只有一个linux-x86_64-normal-server-release目录,再进去就能看到JDK目录了,如下图:


进入/usr/local/openjdk/build/linux-x86_64-normal-server-release/jdk/bin目录,会发现里面有Java文件,执行./java -version输出如下:


新的JDK信息已经打印出来了,OpenJDK Runtime Enviroment信息已经说明了这是个最新构建的JDK环境。



至此我们的本次实战就结束了,面对如此干净的编译环境和现成的源码,读者您是否有一种改动一番源码的冲动,然后构建一个个性化的属于自己的JDK,建议您自己动手修改和编译OpenJDK源码,以加深对JVM的认识。

国内镜像源解决方式

徐新坤 发表了文章 • 0 个评论 • 207 次浏览 • 2019-06-17 09:25 • 来自相关话题

Azure China提供了目前用过的质量最好的镜像源,涵盖了docker.io,gcr.io,quay.io。无论是速度还是覆盖范围,体验都极佳。而且都支持匿名拉取,也就是不需要登录。这点特别友好。azk8s.cn支持的镜像代理转换如下列表。 ...查看全部

Azure China提供了目前用过的质量最好的镜像源,涵盖了docker.io,gcr.io,quay.io。无论是速度还是覆盖范围,体验都极佳。而且都支持匿名拉取,也就是不需要登录。这点特别友好。azk8s.cn支持的镜像代理转换如下列表。






globalproxy in Chinaformatexample
dockerhub (docker.io)dockerhub.azk8s.cndockerhub.azk8s.cn//:dockerhub.azk8s.cn/microsoft/azure-cli:2.0.61 dockerhub.azk8s.cn/library/nginx:1.15
gcr.iogcr.azk8s.cngcr.azk8s.cn//:gcr.azk8s.cn/google_containers/hyperkube-amd64:v1.13.5
quay.ioquay.azk8s.cnquay.azk8s.cn//:quay.azk8s.cn/deis/go-dev:v1.10.0



Note:
k8s.gcr.io would redirect to gcr.io/google-containers, following image urls are identical:



k8s.gcr.io/pause-amd64:3.1
gcr.io/google_containers/pause-amd64:3.1



这里,我开发了一个小的脚本azk8spull,这个脚本可以自动根据镜像名称进行解析,转换为azure的mirror镜像源域名。并进行拉取。拉取完成后会自动进行tag重命名为原本的镜像名。该脚本已经开源在 https://github.com/xuxinkun/littleTools#azk8spull 上。以下是使用样例。


[root@node-64-216 ~]# azk8spull quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1
## azk8spull try to pull image from mirror quay.azk8s.cn/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1.
0.24.1: Pulling from kubernetes-ingress-controller/nginx-ingress-controller
Digest: sha256:76861d167e4e3db18f2672fd3435396aaa898ddf4d1128375d7c93b91c59f87f
Status: Image is up to date for quay.azk8s.cn/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1
## azk8spull try to tag quay.azk8s.cn/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1 to quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1.
## azk8spull finish pulling.

[root@node-64-216 ~]# azk8spull k8s.gcr.io/pause-amd64:3.1
## azk8spull try to pull image from mirror gcr.azk8s.cn/google_containers/pause-amd64:3.1.
3.1: Pulling from google_containers/pause-amd64
Digest: sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610
Status: Image is up to date for gcr.azk8s.cn/google_containers/pause-amd64:3.1
## azk8spull try to tag gcr.azk8s.cn/google_containers/pause-amd64:3.1 to k8s.gcr.io/pause-amd64:3.1.
## azk8spull finish pulling.



参考资料: 


- Docker Registry Proxy Cache 帮助 http://mirror.azure.cn/help/docker-registry-proxy-cache.html 

- Azure China container registry proxy https://github.com/Azure/container-service-for-azure-china/tree/master/aks#22-container-registry-proxy


原文链接:docker/kubernetes国内源/镜像源解决方式

使用Docker高效搭建开发环境

齐达内 发表了文章 • 0 个评论 • 497 次浏览 • 2019-06-04 23:00 • 来自相关话题

【编者的话】Docker作为轻量级的基于容器的解决方案,它对系统侵入性低,容易移植,天生就适合做复杂业务部署和开发环境搭建,今天给大家带来的是奇虎360开发是如何使用Docker高效搭建开发环境的。 作为一个平时喜 ...查看全部

【编者的话】Docker作为轻量级的基于容器的解决方案,它对系统侵入性低,容易移植,天生就适合做复杂业务部署和开发环境搭建,今天给大家带来的是奇虎360开发是如何使用Docker高效搭建开发环境的。



作为一个平时喜欢折腾的开发人员,我喜欢尝试各种环境,使用感兴趣的各种开源软件。



同时,我也是有一些相对的小洁癖,很喜欢Linux中权限最小化原则,我也不喜欢自己的环境中有太多不知道的东西。



做了多年的Web开发,我接触到的环境大致如下:




  1. 操作系统从CentOS 5到CentOS 7;

  2. Web Server从Apache到Nginx;

  3. 开发语言从最初的PHP 5.2到PHP 7,又到现在主要使用Go,马上还会开始接触C++;

  4. 数据库从MySQL 5.1到现在的5.7,前阵子又开始折腾MariaDB;

  5. Cache选型从Memcache到Redis;

  6. 队列用过Kafka,去年开始大量使用NSQ;



公司虽然有专门负责部署、运维这些服务的同学,但我在开发的时候,还是喜欢自己来搭建这些东西,因为这样通常可以对使用到的服务有更多的认识,也能帮助自己使用的更好。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态



今天我就来和大家分享下我是如何高效的搭建好自己的开发环境的。





搭建前说明



这里先说明一点,对每个开源软件,我几乎都是自己编译部署的,而不会使用类似yum install这种方式,也很少直接下载官方编译好的二进制包,这都是为了能多深入了解用到的开源软件。



但一些依赖的动态库文件,如zlib等,还有编译工具,如GCC、make等,我都是通过方便的yum install这种方式直接安装的,否则会累死。





传统做法



我在很长的一段时间内,都是把每个软件的编译、安装过程写成一个脚本,之后再需要用的时候直接运行脚本即可,但这样的方式,通常会遇到下面这些问题:




  1. 脚本只能在我当时的操作系统环境下运行。记得当时购买过不同服务商的VPS,虽然不同VPS我都使用同样的Linux发行版,但脚本通常都不能一键跑完。这也是没办法,因为每个VPS服务商都会制作自己的操作系统镜像版本。

  2. 操作系统升级,如CentOS 5 - 6,或是换为Ubuntu,这样基本上脚本都跑不了。

  3. 软件升级,如MySQL 5.2 - 5.6,构建工具改为CMake,依赖库改变或升级。

  4. 如果某个软件依赖的公共库版本和其它软件不同,且公共库升级后和旧版不兼容,那你就只能为这个软件单独编译公共库了,如果只是普通的公共库还好,但如果是所需要的编译工具版本不同,那可就惨了。



上面这些问题,如果你想每个发行版维护一个脚本,那会累死,因为一旦你每次想升级一个软件,难道每个发行版都要编译一遍吗?这就变成了收获价值很低的体力劳动了。



由于喜欢折腾的个性,我对操作系统的升级以及软件包版本的升级又经常发生,所以一直以来,我都在寻找一个好方法,能很方便的维护好自己的开发环境,尽量做到只=新东西只为它工作一次,最后我找到了Docker,目前我都是用它来搭建自己的开发环境的。





Docker做法



先概括介绍下我的方法:




  1. 让每个软件运行在容器中,因为运行的容器环境是可以固定下来的,所以编译安装脚本写一个就可以了。

  2. 代码使用数据卷的方式加载到需要的容器中。

  3. 因为是开发环境,所以网络方面使用最简单的–net=host。

  4. 将镜像的创建、容器的启动维护在Git项目中,并抽象出统一的构建过程,很方面的做到新软件接入,新机器部署。



下面用实例来说明把:





示例Nginx环境构建



我将构建过程放到Git中:https://gitee.com/andals/docker-nginx



Readme中记录了构建所需要执行的脚本命令,大家访问上面的网址就可以看到,这里我简单介绍下项目的结构:





├── Dockerfile        //创建镜像的Dockerfile
├── pkg //编译好的二进制包,可以直接使用,此外软件运行的一些配置文件或第三方包也放在这里
│ ├── conf
│ │ ├── fastcgi.conf
│ │ ├── http.d
│ │ ├── include
│ │ ├── koi-utf
│ │ ├── koi-win
│ │ ├── logrotate.conf
│ │ ├── logrotate.d
│ │ ├── mime.types
│ │ ├── Nginx.conf
│ │ ├── scgi_params
│ │ ├── uwsgi_params
│ │ └── win-utf
│ ├── luajit-2.0.3.tar.gz
│ └── Nginx-1.8.1.tar.gz
├── README.md
├── script //里面放构建脚本
│ ├── build_image.sh //构建镜像使用
│ ├── build_pkg.sh //编译软件包时使用
│ ├── init.sh //容器启动时执行
│ └── pre_build.sh //软件依赖的共享库,编译和构建时都会用到
└── src //编译时需要的软件源码
├── modules
│ ├── ngx_devel_kit-0.2.19.tgz
│ ├── ngx_echo-0.53.tgz
│ └── ngx_lua-0.9.7.tgz
├── Nginx-1.8.1.tar.gz
└── openssl-1.0.2h.tar.gz




DockerFile说明



Dockerfile结构如下:





FROM andals/CentOS:7
MAINTAINER ligang

LABEL name="Nginx Image"
LABEL vendor="Andals"

COPY pkg/ /build/pkg/
COPY script/ /build/script/

RUN /build/script/build_image.sh

CMD /build/script/init.sh


整个构建框架为:




  1. 把构建需要的包(PKG目录中)放到镜像中

  2. 把构建脚本放到镜像中

  3. 执行构建脚本

  4. 容器启动时,执行init.sh,里面启动相应的服务



Readme.md中记录了执行构建的命令和容器运行命令,示例运行如下:





ligang@vm-xUbuntu16 ~/devspace/dbuild $ git clone git@gitee.com:andals/docker-Nginx.git Nginx
Cloning into 'Nginx'...
......

ligang@vm-xUbuntu16 ~/devspace/dbuild $ cd Nginx/
ligang@vm-xUbuntu16 ~/devspace/dbuild/Nginx $ ngxVer=1.8.1
ligang@vm-xUbuntu16 ~/devspace/dbuild/Nginx $ docker build -t andals/Nginx:${ngxVer} ./
Sending build context to Docker daemon 30.7MB
Step 1/8 : FROM andals/CentOS:7
......
Successfully built ea8147743031
Successfully tagged andals/Nginx:1.8.1

ligang@vm-xUbuntu16 ~/devspace/dbuild/Nginx $ docker run -d --name=Nginx-${ngxVer} --volumes-from=data-home -v /data/Nginx:/data/Nginx --net=host andals/Nginx:${ngxVer}
dbf3c0617eb34c4b1b4ea54c2961989612d5474db3b1acd1d717221e6e5cb516


说明: –volumes-from=data-home这个就是我放置代码的数据卷,我喜欢把代码放到$HOME下面,所以这个卷的相关信息如下:





ligang@vm-xUbuntu16 ~ $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
578912a08ea7 andals/CentOS:7 "echo Data Volumn Ho…" 9 days ago Exited (0) 9 days ago data-home
......

ligang@vm-xUbuntu16 ~ $ docker inspect 578912a08ea7
......
"Mounts": [
{
"Type": "bind",
"Source": "/home",
"Destination": "/home",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
......


/data/Nginx中放置Nginx的conf、log等,每个软件运行时的conf、log、data等我都统一放置在/data下面,如:





ligang@vm-xUbuntu16 ~ $ tree -d /data/ -L 1
/data/
├── mariadb
├── Nginx
└── redis

ligang@vm-xUbuntu16 ~ $ tree -d /data/Nginx/
/data/Nginx/
├── conf
│ ├── http.d
│ ├── include
│ └── logrotate.d
└── logs


启动容器时使用–net=host,作为开发环境简单实用 我就是通过这种方法完成了开发环境的构建,不再有多余的重复工作,并且新机器部署开发环境效率极高。



我目前用到的容器环境如下:





ligang@vm-xUbuntu16 ~ $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dbf3c0617eb3 andals/Nginx:1.8.1 "/bin/sh -c /build/s…" 33 minutes ago Up 33 minutes Nginx-1.8.1
3e31ef433298 andals/php:7.1.9 "/bin/sh -c /build/s…" 8 hours ago Up 8 hours php-7.1.9
360f94bf9c43 andals/jekyll:latest "/bin/sh -c /build/s…" 9 days ago Up 10 hours jekyll-latest
0a7d58d1ca5e andals/redis:4.0.8 "/bin/sh -c /build/s…" 9 days ago Up 10 hours redis-4.0.8
fdaa655b4a11 andals/samba:4.4.16 "/bin/sh -c /build/s…" 9 days ago Up 10 hours samba-4.4.16
6ad00a69befd andals/mariadb:10.2.14 "/bin/sh -c /build/s…" 9 days ago Up 10 hours mariadb-10.2.14
578912a08ea7 andals/CentOS:7 "echo Data Volumn Ho…" 9 days ago Exited (0) 9 days ago data-home




辅助工具dockerbox



使用Docker环境后,有个问题,就是没有办法很方便的和软件交互。 这是因为软件都执行在容器中,比如重启Nginx吧,需要下面这几步:




  1. 找到Nginx这个容器

  2. 进入Nginx这个容器

  3. 在容器里面再执行reload



也可以是:




  1. 找到Nginx这个容器

  2. 使用docker exec



但无论哪种方式,都比原先直接执行命令麻烦的多。



另外,有时也需要进入容器中,查看服务的运行情况。



为了方便的做这些事情,我开发了一个工具dockerbox,可以很方便的做到这些事情。



dockerbox的详情及使用方法请见:https://github.com/ligang1109/dockerbox





配置开机运行



最后再说下如何配置开机启动。 我使用虚拟机搭建的开发环境,所以配置这个会省事好多,我使用用了systemd:





ligang@vm-xUbuntu16 ~ $ ll /lib/systemd/system/dstart.service
-rw-r--r-- 1 root root 229 43 21:35 /lib/systemd/system/dstart.service

ligang@vm-xUbuntu16 ~ $ cat /lib/systemd/system/dstart.service
[Unit]
Description=Docker Container Starter
After=network.target docker.service
Requires=docker.service

[Service]
ExecStart=/usr/local/bin/dbox -dconfPath=/home/ligang/.dconf.json start all

[Install]
WantedBy=multi-user.target


dbox请参考dockerbox的使用方法。





结束语



上面说的是我现在使用的开发环境搭建方法,有兴趣爱折腾的同学不妨试试看,如果你有更好的方法,也希望能分享给我。



原文链接:使用Docker高效搭建开发环境

Kubernetes IN Docker - local clusters for testing Kubernetes

老马 发表了文章 • 0 个评论 • 288 次浏览 • 2019-06-04 21:52 • 来自相关话题

Brief Kind(Kubernetes IN Docker是一个用来快速创建 ...查看全部

Brief



Kind(Kubernetes IN Docker是一个用来快速创建和测试kubernetes的工具,Kind把环境的依赖降低到了最小,仅需要机器安装了Docker即可。



Kind 可以做什么?




  • 快速创建一个或多个Kubernetes集群(几分钟)

  • 支持HA Master部署高可用的Kubernetes集群

  • 支持从源码构建并部署一个Kubernetes集群

  • 可以快速低成本体验一个最新的Kubernetes集群,并支持Kubernetes的绝大部分功能

  • 支持本地离线运行一个多节点集群



Kind 有哪些优势?




  • 最小的安装依赖,仅需要安装Docker即可

  • 使用快速简单,使用kind cli工具即可快速创建集群

  • 使用container来 mockkubernetes node

  • 内部使用kubeadm的官方主流部署工具

  • 使用了containerd

  • 通过了CNCF官方的k8s conformance测试





Usage





GO111MODULE="on" go get sigs.k8s.io/kind@v0.3.0 && kind create cluster




How it work



Kind 使用一个 container 来模拟一个 Node,在 Container 里面跑了 systemd ,并用 systemd 托管了 kubelet 以及 containerd,然后容器内部的 kubelet 把其他 Kubernetes 组件,比如 kube-apiserver、etcd、CNI 等组件跑起来。



可以通过配置文件的方式,来通过创建多个 container 的方式,来模拟创建多个 Node,并以这些 Node 来构建一个多节点的 Kubernetes 集群。



Kind 内部使用了 kubeadm 这个工具来做集群的部署,包括 ha master 的高可用集群,也是借助 kubeadm 提供的aplha特性提供的。同时,在 HA Master 下,额外部署了一个 Nginx 用来提供负载均衡 vip。





Build Images



Kind 的镜像分为两个,一个 Node 镜像,一个 Base 镜像。



Node 镜像



Node 镜像的构建比较复杂,目前是通过运行 Base 镜像,并在 Base 镜像内执行操作,再保存此容器内容为镜像的方式来构建的,包含的操作有:




  • 构建 Kubernetes 相关资源(比如二进制文件和镜像)

  • 运行一个用于构建的容器

  • 把构建的 Kubernetes 相关资源复制到容器里

  • 调整部分组件配置参数,以支持在容器内运行

  • 预先拉去运行环境需要的镜像

  • 通过 docker commit 方式保存当前的构建容器为 node 镜像



具体的逻辑,可以参考node.go



Base 镜像



Base 镜像目前使用了 Ubuntu 19.04 作为基础镜像,做了下面的调整:




  • 安装 systemd 相关的包,并调整一些配置以适应在容器内运行

  • 安装 Kubernetes 运行时的依赖包,比如 conntrack、socat、CNI

  • 安装容器



运行环境,比如 containerd、crictl




  • 配置自己的 ENTRYPOINT 脚本,以适应和调整容器内运行的问题



具体的逻辑,可以参考构建的Dockerfile





Create Cluster



Kind 创建集群的基本过程为:




  1. 根据传入的参数,来创建 container,分为 control node 和 worker node 两种(如果是 ha master,还有一个 loadbalancer node)

  2. 如果需要,配置 loadbalancer 的配置,主要是 Nginx 配置文件

  3. 生成 kubeadm 配置

  4. 对于第一个控制节点,使用 kubeadm init 初始化单节点集群

  5. 配置安装 CNI 插件

  6. 配置存储(实际是安装了一个使用 hostpath 的 storageclass)

  7. 其他的控制节点,通过kubeadm join --experimental-control-plane的方式来扩容控制节点

  8. 通过 kubeadm join 扩容其他的工作节点

  9. 等待集群创建完成

  10. 生成访问配置,打印使用帮助具体的创建流程,可以参考代码create.go



这里关于每个容器,是如何作为 Node 跑起来的,可以简单讲解些原理:



根据不同的角色,调用不同的函数创建节点nodes.go





// TODO(bentheelder): remove network in favor of []cri.PortMapping when that is in
func (d *nodeSpec) Create(clusterLabel string) (node *nodes.Node, err error) {
// create the node into a container (docker run, but it is paused, see createNode)
// TODO(bentheelder): decouple from config objects further
switch d.Role {
case constants.ExternalLoadBalancerNodeRoleValue:
node, err = nodes.CreateExternalLoadBalancerNode(d.Name, d.Image, clusterLabel, d.APIServerAddress, d.APIServerPort)
case constants.ControlPlaneNodeRoleValue:
node, err = nodes.CreateControlPlaneNode(d.Name, d.Image, clusterLabel, d.APIServerAddress, d.APIServerPort, d.ExtraMounts)
case constants.WorkerNodeRoleValue:
node, err = nodes.CreateWorkerNode(d.Name, d.Image, clusterLabel, d.ExtraMounts)
default:
return nil, errors.Errorf("unknown node role: %s", d.Role)
}
return node, err
}


节点(容器)创建时,通过配置 –privileged,挂载 tmpfs,修改主机名等,来运行节点create



func createNode(name, image, clusterLabel, role string, mounts []cri.Mount, extraArgs ...string) (handle *Node, err error) {
runArgs := []string{
"-d", // run the container detached
"-t", // allocate a tty for entrypoint logs
// running containers in a container requires privileged
// NOTE: we could try to replicate this with --cap-add, and use less
// privileges, but this flag also changes some mounts that are necessary
// including some ones docker would otherwise do by default.
// for now this is what we want. in the future we may revisit this.
"--privileged",
"--security-opt", "seccomp=unconfined", // also ignore seccomp
"--tmpfs", "/tmp", // various things depend on working /tmp
"--tmpfs", "/run", // systemd wants a writable /run
// some k8s things want /lib/modules
"-v", "/lib/modules:/lib/modules:ro",
"--hostname", name, // make hostname match container name
"--name", name, // ... and set the container name
// label the node with the cluster ID
"--label", clusterLabel,
// label the node with the role ID
"--label", fmt.Sprintf("%s=%s", constants.NodeRoleKey, role),
}

// pass proxy environment variables to be used by node's docker deamon
proxyDetails := getProxyDetails()
for key, val := range proxyDetails.Envs {
runArgs = append(runArgs, "-e", fmt.Sprintf("%s=%s", key, val))
}

// adds node specific args
runArgs = append(runArgs, extraArgs...)

if docker.UsernsRemap() {
// We need this argument in order to make this command work
// in systems that have userns-remap enabled on the docker daemon
runArgs = append(runArgs, "--userns=host")
}

err = docker.Run(
image,
docker.WithRunArgs(runArgs...),
docker.WithMounts(mounts),
)

// we should return a handle so the caller can clean it up
handle = FromName(name)
if err != nil {
return handle, errors.Wrap(err, "docker run error")
}

return handle, nil
}


More



Kind是一个比较简单有趣的项目,Kind的scope定的比较明确和具体,也定的比较小,其实借助 Kind 或者 Kind 的思想,可以做更多的事情,比如:




  • 在单节点部署自己的上层平台

  • 借助容器 mock 节点的方式,优化现有的测试方案

  • 自动化的部署测试

  • 自动化的 e2e 测试



原文链接:https://mp.weixin.qq.com/s/Vhr0ml1wI1BIoxKqKGTXRg

58集团云平台架构实践与演进

玻璃樽 发表了文章 • 0 个评论 • 457 次浏览 • 2019-06-04 19:23 • 来自相关话题

【编者的话】在17年底,我们分享了《高可用Docker容器云在58集团的实践》这篇文章,对整个容器云基础环境搭建与应用选型进行了详细介绍,本文是在该文章基础之上的进阶篇,是针对具体业务场景的落地解决方案。如果对基础环境选型比较感兴趣,可以查看上篇文章,在本文的 ...查看全部
【编者的话】在17年底,我们分享了《高可用Docker容器云在58集团的实践》这篇文章,对整个容器云基础环境搭建与应用选型进行了详细介绍,本文是在该文章基础之上的进阶篇,是针对具体业务场景的落地解决方案。如果对基础环境选型比较感兴趣,可以查看上篇文章,在本文的最后会附上相关文章的链接。对于上篇文章讨论过的内容,本文将不再进行详细讨论。后续每个月,云团队都会选择平台中某一具体领域的相关工作进行详细讨论与分享,欢迎大家关注。大家想了解哪方面的实现方案与细节,可进行相应留言。
#背景

通过容器化技术,58云计算平台主要解决以下几个问题:

  1. 资源利用率低:通过云化技术可以将资源利用率提升至原有的3-4倍,甚至更高。
  2. 服务扩容效率低:将传统扩容的时间从小时级别降低为分钟级别。
  3. 上线流程不规范:基于同一的镜像模板,约束整个上线过程。

为了解决上述问题,云团队通过技术选型与反复论证最终决定基于Docker与Kubernetes体系构建整个容器云环境。

云计算平台的发展历程如下:
1.png

#整体架构

58云计算平台的整体架构如下:
2.png

所有容器云的架构都是相似的,这里不做赘述,具体可查看上篇文章。

云计算平台承载了集团90%以上的业务流量,作为核心的服务管理与上线系统,它并不是独立运作的,为了保证整个上线流程的一致性与流畅度,满足业务日常管理与维护的通用需求,云平台与集团内部多个核心系统与组件进行了相应的对接与联动。
3.jpg

在项目管理方面,云平台与代码管理系统、项目管理等系统进行了内部对接,实现了代码从编译到生成镜像,再到环境部署的完全自动化。

在运维方面,云平台与CMDB、服务树、监控系统等多个运维系统进行了打通与整合,保证整个运维体系的完整性与用户体验的一致性。

在基础组件方面,集团内部的服务治理体系与常用的中间件系统都针对云平台进行了相应的改造,以适配云平台的工作模式,同时云平台也集成了现有的数据收集组件,保证数据流与用户习惯在云化前后是一致的。

云平台的使用方面,平台定义了四套环境,四套环境基于唯一的镜像仓库,这使得同一个代码版本可以在不同的环境之间进行流转,在保证代码唯一性的同时也使得相关环境的变更不会传递到下一个环境。
4.png

#架构演进

构建一个适配多种业务场景的容器云有很多细节需要考虑,云团队做了很多工作,由于篇幅关系,这里主要和大家分享58云计算平台在“网络架构”和“服务发现”两个核心组件上的架构实践与演进。
##网络架构

在网络架构方面,通过对比常用的六种容器组网模型,云计算平台选择“bridge+vlan”的方式作为基础网络模型。
5.png

原生bridge网络模型存在两个明显的缺点:IP利用率低、缺少网络限速。

为了解决IP地址利用率低的问题,云平台基于docker的CNM接口开发了网络插件,支持多个宿主间共享同一个容器网段,也支持IP地址在不同的宿主间复用。
6.png

这种网络模式下,分配给业务实例的IP地址是随机的,实例每次重启IP地址都可能会发生变更。在推进业务云化的早期,这种模式被业务所接受,但是随着云化进程的不断深入,业务方面提出了更高的要求:固定IP。

业务需求来源于真实的案例:有些服务要求IP不能发生变化,特别是某些依赖于第三方外部接口的服务。同时集团内部很多系统都是以IP固定为前提,如果IP随机分配,这些现有系统将不可用或很难用,极大的影响用户体验。如果IP固定的需求不能被满足,业务云化将很难推进下去。所以在18年4月份,云平台对网络架构进行了升级,支持了固定IP模式。网络架构的升级很好的保证了业务云化的进程。
7.png

固定IP的网络架构基于Kubernetes的CNI接口实现,增加了IP控制器模块,业务每次扩缩容时,都会与IP控制器交互,进行IP的变更。在业务正常升级流程中,归属于业务的IP将不会发生变化。依托于腾讯机房的网络支撑,平台将容器网段的路由规则下发到交换机,实现了容器的全网漂移,而不仅仅局限于固定的交换机。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

针对网络限速的需求,云平台结合容器网络虚拟化的特性,基于自研监控与tc工具实现了容器网络限速的能力。
8.jpg

标准的tc工具只支持单向限速,即出口流量限速。单方向限速无法满足业务的实际需求。在深入研究容器网络虚拟化的原理后,我们发现,容器在做虚拟化时,会创建一个网卡对,他们是对等的网络设备,在宿主机上体现为veth,在容器内部体现为eth0。基于这个特性,我们实现了双向限速:即对两块网卡同时做出口流量限速。由于是对等网卡,所以对veth做出口流量限速就相当于对eth0做入口流量限速。

在网络限速的基础上,云平台又进行了多维度的完善,分别支持动态限速、秒级限速与弹性限速等网络应用场景,极大的实现带宽复用。
##服务发现

服务发现是容器云平台中非常核心的服务组件,是流量的入口,也是服务对外暴露的接口。云平台中IP动态分配,节点弹性伸缩,需要有一套自动变更负载均衡器的方式。对于后端服务,集团有成熟的服务治理框架与体系,在云化过程中,中间件团队对服务治理体系进行了改造使其可以适配不断变化的云环境。对于前端服务,集团是基于Nginx做负载均衡,并且没有一套支持IP自动变更的架构。为了实现前端服务的云化,云平台团队与运维团队共同设计了全新的服务发现架构。

在调研阶段,云团队也调研了Kubernetes自带的服务发现机制,这一机制无法提供复杂的负载均衡策略,并且无法满足业务在特殊场景下需要对部分节点摘除流量的需求,所以最终我们否定了这一方案。
9.png

这是业界典型的服务发现架构。服务端和负载均衡器通过Consul进行解耦。服务注册在Consul中,负载均衡器通过Watch Consul中目录的变化来实时感知节点变更。在云化的早期,为了满足快速上线的需求,云平台对集团内部的Java Web框架进行了修改,使得其以心跳的方式自动注册到Consul中。这很好的解决了负载均衡器自动感知业务变更的问题以及云化过程中的流量灰度问题。

这一架构也引入了新的问题:调试问题与多语言扩展问题。由于是Java框架代理业务注册到Consul中,这使得活跃节点无法被下掉流量从而进行调试,但实际场景中很多故障调试需要下掉流量后才能进行。对Java语言的支持是通过修改框架来完成的,而集团中还有很多其他类型语言,比如PHP、Node.js和Go等。对每种接入语言或框架都需要以修改代码的方式才能接入到云平台中来,开发成本高并且对业务不友好。Consul被直接暴露给服务方,也增加了Consul的安全风险。

基于此,在18年4月,云平台对整个服务发现架构进行了升级。
10.png

新的服务发现架构中,业务与Consul中间增加了Proxy代理层,代理层通过Watch Kubernetes事件实时感知业务节点信息的变化,配合健康检查功能可以保证业务节点在变更时流量无损。基于代理层,任意语言的程序不需要做任何修改,仅通过简单配置即可接入到云平台。服务的注册与发现托管到云平台,Consul组件对业务透明,开发人员使用更友好。
#复盘与反思

回顾这两年来的容器云架构演进过程与业务云化历程,复盘遇到的棘手问题及其解决方案,与大家共同探讨。

在整个云平台的设计之初,我们有如下的设计考量:
11.png

容器云都会面临一个问题:对于业务来说,是容器还是虚拟机?虚拟机是业务习惯的使用模式,业务更容易接受。容器是以服务为核心,服务存在即存在,服务关闭即销毁,不再是虚拟机以机器为核心的模式。思虑再三,最终决定以容器的模式来定位,全新的平台提供全新的服务模式与体验。

虽然是以容器为核心,但容器中可运行单进程也可运行多进程。我们规范容器中只运行一个业务进程,防止由于运行多进程而导致业务之间相互干扰情况的发生,这类问题很难进行排查与定位。

去Agent化:在传统物理机模式下,一台物理机上可能会有多个Agent:运维管控Agent、监控Agent、业务自定义Agent等。云化后面临一个现实的问题:我们需要在每个容器中都安装这些Agent么?如果每个容器中都集成这些Agent,对云平台来说是简单的,这没有任何工作量。但是一台物理机上可能会运行上百个容器,这些Agent数量也会大量膨胀,带来系统负载的增加的问题。基于此,云平台投入大量的精力来实现容器的无Agent化,即在所有功能正常运行的前提下,Agent只运行在宿主上,不会运行在容器中。

业务云化过程中云团队遇到了很多问题案例,并形成自己独特的解决方案。这里选择了几个有代表性的案例,与大家进行分享。
12.png

服务启动耗CPU过高是云化早期时遇到的棘手问题。这个问题产生的原因很多:Java的语言特性导致JVM是在运行中逐步进行优化的;内部的很多开发框架都是在流量过来时才初始化链接;某些业务资源也是在流量过来时才进行初始化。当流量分发过来时,多种资源同时初始化导致服务需要大量的CPU资源,超过了平台为其分配的CPU配额,服务出现大量超时与抛弃。这一问题的简单解法是多分配CPU资源,但从长远角度来看这会导致资源利用无法有效把控,也会影响弹性调度的效果。最终,我们从两个维度解决这一问题:在调用方增加预热策略,流量逐步分发过来,CPU资源使用更平滑;在服务方增加预热方法,默认初始化链接等资源,同时引导用户进行自定义的初始化。

在容器监控维度方面,由于默认的监控数据是基于随机采样的分钟级数据,导致当服务出现短期秒级的CPU波动时,监控系统无法捕获,从而影响问题的排查与定位。针对这一问题,云平台对监控维度进行了深化,增加了容器级别秒级监控,实时采集每分钟的最大值上报,便于问题的排查与跟踪。
13.png

由于Cgroups只对CPU和内存资源进行了隔离与限速,并没有对系统负载进行隔离,如果容器中运行进程或线程数过多,会导致宿主机整体的负载波动,进而对上面的所有服务都会造成影响。

云平台增加了两个维度的控制策略:容器级别的最大线程数控制,防止由于单个容器线程数过多会对宿主造成影响。宿主级别的过载保护,当宿主上的负载过高时,过载保护策略会自动触发宿主上的容器进行漂移,直至负载降至合理的范围。

云平台必须关闭swap交换分区,这是一个深刻的经验教训。在云化的早期,云平台的交换分区没有关闭,部分服务经常出现由于使用交换分区而导致的耗时随机抖动,这类问题很难排查,耗费了大量的精力。

58云计算平台核心软件版本变迁过程如下:
14.png

#后记

容器云在58集团的实践与探索过程中,云团队做了很多技术选型与优化工作,也进行了很多技术方案的架构演进工作,究其根本原因是58集团是发展了十多年的互联网公司,它有自己独特的业务场景和成熟的开发体系与基础组件,容器云在集团内部起步较晚,内部系统很难为云平台做适配,只能云平台通过不断演进来适配不停发展的业务场景。这也是在较有规模的互联网公司做基础架构与创业公司的区别和挑战所在。所幸在团队的共同努力下,我们用1年多的时间完成了集团流量服务的云化工作。目前,集团内部还有很多服务需要云化,未来的挑战更大。

原文链接:https://mp.weixin.qq.com/s/gJM5-DByvMH54QhVsJ5HEA

把数据库放入Docker是一个好主意吗?

Andy_Lee 发表了文章 • 0 个评论 • 372 次浏览 • 2019-06-04 11:33 • 来自相关话题

一个未打补丁的漏洞影响所有版本的 Docker !

齐达内 发表了文章 • 0 个评论 • 236 次浏览 • 2019-05-31 15:49 • 来自相关话题

所有版本的Docker目前都容易受到一种竞态条件(race condition)的攻击,这种竞态条件可能使攻击者对主机系统上的任何文件拥有读取权限和写入权限。概念验证代码已发布。 该漏洞类似CVE-2018-15664,它为黑客修改资 ...查看全部
所有版本的Docker目前都容易受到一种竞态条件(race condition)的攻击,这种竞态条件可能使攻击者对主机系统上的任何文件拥有读取权限和写入权限。概念验证代码已发布。

该漏洞类似CVE-2018-15664,它为黑客修改资源路径提供了一个机会窗口,这个机会窗口出现在路径被解析之后,但被分配的程序开始对资源进行操作之前的时间点。这被称为检查时间/使用时间(TOCTOU)类型的漏洞。
##访问主机上的文件
究其核心,该漏洞源于FollowSymlinkInScope函数,该函数容易受到基本的TOCTOU攻击的影响。这个函数的目的是如同进程在Docker容器的内部那样对待进程,以一种安全的方式来解析指定的路径。

并不立即针对解析的路径进行操作,而是“稍微过一段时间进行操作”。攻击者可以推测这个时间差,添加一条符号链接(symlink)路径,该路径可能最终解析拥有root权限的主机。

这可以通过“docker cp”实用程序来实现,该实用程序允许在容器和本地文件系统之间复制内容。早在2014年就出现过类似的漏洞。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

Suse的高级软件工程师Aleksa Sarai在安全公告中写道:“据我所知,对这种攻击没有任何有意义的防护(除了不允许对运行中的容器执行docker cp,但这只有助于应对通过FollowSymlinkInScope实现的特定攻击。)除非你通过AppArmor限制了Docker守护进程,否则它会影响主机文件系统。”
##应对方法和漏洞脚本
在Hacker News网站上,一则讨论帖子提供了潜在的应对方法,不过它们有赖于实际环境的背景和目的。

Sarai提议的一种应对办法是修改“chrootarchive”,以便归档操作在安全的环境中运行,其中root是容器“rootfs”。这需要更改Docker的核心部分,因此这不可行。

其次的办法是在使用文件系统时暂停容器。这无法阻止所有攻击,但可以防御较基本的攻击。补丁已向上游提交,目前仍在审核中。

这位工程师还编写了两个漏洞脚本:一个脚本用于读取访问,另一个用于写入访问,其中二进制代码通过运行“a RENAME_EXCHANGE of a symlink to "/" and an empty directory in a loop”,试图达到竞态条件。Sarai称,这两个脚本的目的是将文件复制到含有修改后的符号链接的路径,或从该路径复制文件。

从处于竞态条件下的主机系统读取任意内容的攻击代码其成功率不到1%。这个成功率可能看起来很低,但实际上相当于等待10秒钟攻击就能得逞。

有了将资源写入到主机上的脚本,就有可能“仅用极少的迭代就可以覆盖主机文件系统”。

在公开宣布这个漏洞之前,Sarai与Docker安全团队讨论了这个问题,最后得出了披露是合理的这一结论。

原文链接:https://mp.weixin.qq.com/s/1d18fBAKJj5LYymSzpFECA

Docker环境的持续部署优化实践

JetLee 发表了文章 • 0 个评论 • 213 次浏览 • 2019-05-31 14:48 • 来自相关话题

#背景介绍 那年公司快速成长,频繁上线新项目,每上线一个项目,就需要新申请一批机器,初始化,部署依赖的服务环境,一个脚本行天下。 那年项目发展如火如荼,A项目流量暴增马上给A扩机器,B项目上线新功能又要扩容B,上线新 ...查看全部
#背景介绍

那年公司快速成长,频繁上线新项目,每上线一个项目,就需要新申请一批机器,初始化,部署依赖的服务环境,一个脚本行天下。

那年项目发展如火如荼,A项目流量暴增马上给A扩机器,B项目上线新功能又要扩容B,上线新项目没资源了,就先下线处于流量低峰的C项目主机。

每天日夜加班,疲于奔命。

那年得知了Docker能拯救我于水火,遂决定为了荣誉(发际线)而战。

为了快速落地以及尽量降低引入Docker对整个CICD流程的影响,用最小的改动把Docker加入到了我们上线的流程中,流程变化参考下图:

1.png


那年容器编排江湖混战,K8S还不流行,加之时间精力有限,技术实力也跟不上,生产环境没敢贸然上线编排,单纯在之前的主机上跑了Docker,主要解决环境部署和扩容缩容的问题,Docker上线后也确实解决了这两块的问题,还带来了诸如保证开发线上环境一致性等额外惊喜。

但Docker的运用也并不是百利而无一害,将同步代码的方式转变成打包镜像、更新容器也带来了上线时间的增长,同时由于各个环境配置文件的不同也没能完全做到一次打包多环境共用,本文主要介绍我们是如何对这两个问题进行优化的。
#Python多线程使用

分析了部署日志,发现在整个部署过程中造成时间增长的主要原因是下载镜像、重启容器时间较长。

整个部署程序由Python开发,核心思想是用paramiko模块来远程执行ssh命令,在还没有引入Docker的时候,发布是rsyslog同步代码,单线程滚动重启服务,上线Docker后整个部署程序逻辑没有大改,只是把同步代码重启服务给换成了下载镜像重启容器,代码大致如下:
import os
import paramiko

# paramiko.util.log_to_file("/tmp/paramiko.log")
filepath = os.path.split(os.path.realpath(__file__))[0]


class Conn:
def __init__(self, ip, port=22, username='ops'):
self.ip = ip
self.port = int(port)
self.username = username

self.pkey = paramiko.RSAKey.from_private_key_file(
filepath + '/ssh_private.key'
)

def cmd(self, cmd):
ssh = paramiko.SSHClient()

try:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(self.ip, self.port, self.username, pkey=self.pkey, timeout=5)
except Exception as err:
data = {"state": 0, "message": str(err)}
else:
try:
stdin, stdout, stderr = ssh.exec_command(cmd, timeout=180)
_err_list = stderr.readlines()

if len(_err_list) > 0:
data = {"state": 0, "message": _err_list}
else:
data = {"state": 1, "message": stdout.readlines()}
except Exception as err:
data = {"state": 0, "message": '%s: %s' % (self.ip, str(err))}
finally:
ssh.close()

return data


if __name__ == '__main__':
# 演示代码简化了很多,整体逻辑不变

hostlist = ['10.82.9.47', '10.82.9.48']
image_url = 'ops-coffee:latest'

for i in hostlist:
print(Conn(i).cmd('docker pull %s' % image_url))
# 在镜像下载完成后进行更新容器的操作,代码类似省略了

全部都是单线程操作,可想效率就不会很高,为什么不用多线程?主要还是考虑到服务的可用性,一台服务器更新完成再更新下一台服务器直到所有服务器更新完成,单线程滚动更新最大程度保证服务可用,如果同时所有服务器进行更新,那么服务重启过程中无法对外提供服务,系统会有宕机的风险,且当时项目规模都很小,忽略掉了这个时间的增加,随着项目越来越多,规模越来越大,不得不重新思考这块的优化。

引入多线程势在必行,那么多线程该如何应用呢?从服务整体可用性考虑,把下载镜像跟重启容器两个操作拆分,下载镜像不影响服务正常提供,完全可以采用多线程,这样整个下载镜像的时间将大大缩短,优化后代码如下:
import threading
# 再导入上一个示例里边的Conn类

class DownloadThread(threading.Thread):

def __init__(self, host, image_url):
threading.Thread.__init__(self)
self.host = host
self.image_url = image_url

def run(self):
Conn(self.host).cmd('docker login -u ops -p coffee hub.ops-coffee.cn')
r2 = Conn(self.host).cmd('docker pull %s' % self.image_url)
if r2.get('state'):
self.alive_host = self.host
print('---->%s镜像下载完成' % self.host)
else:
self.alive_host = None
print('---->%s镜像下载失败,details:%s' % (self.host, r2.get('message')))

def get_result(self):
return self.alive_host


if __name__ == '__main__':
# 演示代码简化了很多,整体逻辑不变

hostlist = ['10.82.9.47', '10.82.9.48']
image_url = 'ops-coffee:latest'

threads = []
for host in hostlist:
t = DownloadThread(host, image_url)
threads.append(t)

for t in threads:
t.start()

for t in threads:
t.join()

alive_host = []
for t in threads:
alive_host.append(t.get_result())
[size=16] 多线程下载镜像结束
[/size]

print('---->本项目共有主机%d台,%d台主机下载镜像成功' % (len(hostlist), len(alive_host)))

重启容器就不能这么简单粗暴的多线程同时重启了,上边也说了,同时重启就会有服务宕机的风险。线上服务器都有一定的冗余,不能同时重启那么可以分批重启嘛,每次重启多少?分析了流量情况,我们想到了一个算法,如果项目主机少于8台,那么就单线程滚动重启,也用不了太长时间,如果项目主机大于8台,那么用项目主机数/8向上取整,作为多线程重启的线程数多线程重启,这样差不多能保证项目里边有80%左右的主机一直对外提供服务,降低服务不可用的风险,优化后的代码如下:
import threading
from math import ceil
# 再导入上一个示例里边的Conn类

class DeployThread(threading.Thread):
def __init__(self, thread_max_num, host, project_name, environment_name, image_url):
threading.Thread.__init__(self)
self.thread_max_num = thread_max_num
self.host = host
self.project_name = project_name
self.environment_name = environment_name
self.image_url = image_url

def run(self):
self.smile_host = []
with self.thread_max_num:
Conn(self.host).cmd('docker stop %s && docker rm %s' % (self.project_name, self.project_name))

r5 = Conn(self.host).cmd(
'docker run -d --env ENVT=%s --env PROJ=%s --restart=always --name=%s -p 80:80 %s' % (
self.environment_name, self.project_name, self.project_name, self.image_url)
)

if r5.get('state'):
self.smile_host.append(self.host)
print('---->%s镜像更新完成' % (self.host))
else:
print('---->%s服务器执行docker run命令失败,details:%s' % (self.host, r5.get('message')))

# check镜像重启状态 and 重启失败需要回滚代码省略

def get_result(self):
return self.smile_host


if __name__ == '__main__':
# 演示代码简化了很多,整体逻辑不变

alive_host = ['10.82.9.47', '10.82.9.48']
image_url = 'ops-coffee:latest'

project_name = 'coffee'
environment_name = 'prod'

# alive_host / 8 向上取整作为最大线程数
thread_max_num = threading.Semaphore(ceil(len(alive_host) / 8))

threads = []
for host in alive_host:
t = DeployThread(thread_max_num, host, project_name, environment_name, image_url)
threads.append(t)

for t in threads:
t.start()

for t in threads:
t.join()

smile_host = []
for t in threads:
smile_host.append(t.get_result())

print('---->%d台主机更新成功' % (len(smile_host)))

经过以上优化我们实测后发现,一个28台主机的项目在优化前上线要花10分钟左右的时间,优化后只要2分钟左右,效率提高80%。
#多环境下配置文件的处理

我们采用了项目代码打包进镜像的镜像管理方案,开发、测试、预发布、生产环境配置文件都不同,所以即便是同一个项目不同的环境都会单独走一遍部署发布流程打包镜像,把不同环境的配置打包到不同的镜像中,这个操作太过繁琐且没有必要,还大大增加了我们的上线时间。

用过Kubernetes的都知道,Kubernetes中有专门管理配置文件的ConfigMap,每个容器可以定义要挂载的配置,在容器启动时自动挂载,以解决打包一次镜像不同环境都能使用的问题,对于没有用到Kubernetes的要如何处理呢?配置中心还是必不可少的。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

我们处理不同配置的整体思路是,在Docker启动时传入两个环境变量ENVT和PROJ,这两个环境变量用来定义这个容器是属于哪个项目的哪个环境,Docker的启动脚本拿到这两个环境变量后利用confd服务自动去配置中心获取对应的配置,然后更新到本地对应的位置,这样就不需要把配置文件打包进镜像了。

以一个纯静态只需要Nginx服务的项目为例。

Dockerfile如下:
FROM nginx:base

COPY conf/run.sh /run.sh
COPY webapp /home/project/webapp

CMD ["/run.sh"]

run.sh脚本如下:
#!/bin/bash
/etc/init.d/nginx start && \
sed -i "s|/project/env/|/${PROJ}/${ENVT}/|g" /etc/confd/conf.d/conf.toml && \
sed -i "s|/project/env/|/${PROJ}/${ENVT}/|g" /etc/confd/templates/conf.tmpl && \
confd -watch -backend etcd -node=http://192.168.107.101:2379 -node=http://192.168.107.102:2379 || \
exit 1

Docker启动命令:
'docker run -d --env ENVT=%s --env PROJ=%s --restart=always --name=%s -p 80:80 %s' % (
self.environment_name, self.project_name, self.project_name, self.image_url)

做到了一次镜像打包多环境共用,上线时也无需再走一次编译打包的流程,只需更新镜像重启容器即可,效率明显提高。
#写在最后


* 缺少编排的容器是没有灵魂的,继续推进编排工具的运用将会是2019年工作的重点。
* 实际上我们在Docker改造稳定后,内网开发测试环境部署了一套k8s集群用到现在已经一年多的时间比较稳定。
* 线上用到了多云环境,一部分线上项目已经使用了基于Kubernetes的容器编排,当然还有一部分是我上边介绍的纯Docker环境。

原文链接:https://mp.weixin.qq.com/s/xnBehfSlZ3J02xb0GFuGDw

用 Docker 快速配置前端开发环境

翔宇 发表了文章 • 0 个评论 • 295 次浏览 • 2019-05-30 21:28 • 来自相关话题

学好Docker,你应该了解这些

老马 发表了文章 • 0 个评论 • 263 次浏览 • 2019-05-30 20:07 • 来自相关话题

Docker公司推出的三剑客:Machine、Compose和Swarm。这三件利器的出现,让Docker不仅支持单机的虚拟化,而且能支持更广泛的集群平台,提供更强大灵活的功能。本文通过简短的介绍,让对Docker三剑客有个初步了解。今后遇到Docker相关问 ...查看全部
Docker公司推出的三剑客:Machine、Compose和Swarm。这三件利器的出现,让Docker不仅支持单机的虚拟化,而且能支持更广泛的集群平台,提供更强大灵活的功能。本文通过简短的介绍,让对Docker三剑客有个初步了解。今后遇到Docker相关问题,知道用什么技术去解决。

docker-machine:解决Docker运行环境问题

Docker技术是基于Linux内核的cgroup技术实现的,那么问题来了,如果在非Linux平台上使用Docker技术需要依赖安装Linux系统的虚拟机。docker-machine就是Docker公司官方提出的,用于在各种平台上快速创建具有Docker服务的虚拟机的技术。可以把它理解为virtualbox或者VMware,最开始在Win7上用得比较多,但是Win10开始自带了hyper-v虚拟机,已经不再需要docker-machine了,Docker可以直接运行在安装了Linux系统得hyper-v上。

dcoker-compose:解决本地Docker容器编排问题

一般是通过yaml配置文件来使用它,这个docker-compose.yml文件里能记录多个容器启动的配置信息(镜像、启动命令、端口映射等),最后只需要执行docker-compose对应的命令,例如docker-compose up就会像执行脚本一样地批量创建和销毁容器。

docker-swarm:解决多主机多个容器调度部署得问题

Swarm是基于Docker平台实现的集群技术,他可以通过几条简单的指令快速的创建一个Docker集群,接着在集群的共享网络上部署应用,最终实现分布式的服务。Swarm技术不是很成熟,很多配置功能都无法实现,只能说是个半成品,目前更多的是使用Kubernetes来管理集群和调度容器。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

总结

如果你是在非Linux环境下考虑使用docker-machine,当然我更推荐使用hyper-v或者virtualbox。如果你需要同时操作多个容器,或者希望使用配置文件记录容器启动命令参数,那么推荐使用docker-compose。如果你需要在多台主机上部署Docker容器,并对其进行调度,那么Swarm是一种选择,当然更推荐Kubernetes。

原文链接:https://www.jianshu.com/p/b293d076b34b
Docker 是个伟大的项目,它彻底释放了虚拟化的威力,极大降低了云计算资源供应的成本,同时让应用的分发、测试、部署和分发都变得前所未有的高效和轻松!