docker 增加端口怎么增加应用

Docker 教程
Docker 教程
Docker 是一个开源的应用容器引擎,基于
并遵从Apache2.0协议开源。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。
谁适合阅读本教程?
本教程适合运维工程师及后端开发人员,通过本教程你可以一步一步了解 Docker 的使用。
阅读本教程前,您需要了解的知识
在阅读本教程前,你需要掌握 Linux 的常用命令。你可以通过本站的
来学习相关命令。
Docker的应用场景
Web 应用的自动化打包和发布。
自动化测试和持续集成、发布。
在服务型环境中部署和调整数据库或其他的后台应用。
从头编译或者扩展现有的OpenShift或Cloud Foundry平台来搭建自己的PaaS环境。
Docker 的优点
1、简化程序:Docker 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,便可以实现虚拟化。Docker改变了虚拟化的方式,使开发者可以直接将自己的成果放入Docker中进行管理。方便快捷已经是 Docker的最大优势,过去需要用数天乃至数周的 任务,在Docker容器的处理下,只需要数秒就能完成。
2、避免选择恐惧症:如果你有选择恐惧症,还是资深患者。Docker 帮你 打包你的纠结!比如 Docker 镜像;Docker 镜像中包含了运行环境和配置,所以 Docker 可以简化部署多种应用实例工作。比如 Web 应用、后台应用、数据库应用、大数据应用比如 Hadoop 集群、消息队列等等都可以打包成一个镜像部署。
3、节省开支:一方面,云计算时代到来,使开发者不必为了追求效果而配置高额的硬件,Docker 改变了高性能必然高价格的思维定势。Docker 与云的结合,让云空间得到更充分的利用。不仅解决了硬件管理的问题,也改变了虚拟化的方式。
Docker 官网:
Github Docker 源码:
感谢您的支持,我会继续努力的!
扫码打赏,你说多少就多少
记住登录状态
重复输入密码当前位置:
海量应用容器化改造之路
海量应用容器化改造之路
公司内部近千个应用,从需求、开发、测试、发布及后期运维都是各自为营,没有一个统一的平台来标准化应用的整个流程,由此,设计一个这样的标准化平台就极其迫切,同时为了享受当前容器化技术带来的福利,我们决定完全基于容器技术来进行全面的应用容器化改造,以下就是我们团队在平台研发及应用容器化改造过程的一些经历和思考向大家分享一下,思虑不周之处,望大家斧正。
Git Repository:管理应用源码;
Maven Repository:管理应用依赖的外部框架包及内部二方包;
Docker Registry:管理各业务镜像依赖的基础镜像及业务镜像;
Config Center:基于ZK做的实现,管理应用本身的配置文件、Leader控制、分布式锁等;
Rabbit MQ:用于解耦各应用之间的通信;
DB:用于存储应用的一些管理数据和配置数据等;
Kubernetes Cluster:供业务系统运行的一个统一的环境,所有环境的应用全部容器化;
Publish Service:用于管理公司应用,包括用户权限的管理、应用元数据的管理、应用Kubernetes配置元数据的管理、应用的发布、自动扩缩容、手工扩缩容、手工回滚等;
Build Service:用于业务镜像的构建及推送:收到Publish Service发送的构建消息后,从Git Repository拉取应用源码,经过编译、打包、部署、镜像构建及推送到Docker Registry。
构建服务依赖的环境主要包括JDK、Git、Maven、Docker,主要功能包括应用源码检出、编译、打包、部署及Dockerfile生成、镜像构建、推送几个阶段,整个过程我们编写了一个sh脚本,由这个脚本来完成整个过程。
源码检出:接收到发布发送的构建消息时,由JDK中的Process对象来启动这个sh脚本文件,进行应用源码检出,检出来过程有任何异常,发送失败消息给发布服务;
编译:调用Maven的mvn compile命令进行编译,编译过程中有任务异常,发送失败消息给发布服务;
打包:调用Maven的mvn package命令进行打包,打包过程中有任务异常,发送失败消息给发布服务;
调用Maven的mvn deploy命令将打好的包部署到内部Maven仓库,部署过程中有任务异常,发送失败消息给发布服务;如果是二方库应用,流程到些就完成了,如果是非二方库应用则继续5、6、7三步;
Dockerfile生成:Dockerfile每次都是动态生成的,Dockerfile的内部结构基本上比较确定,包括FROM基础镜像、ADD第3步打好的包、EXPOSE端口、ENTRYPOINT java –jar、CMD一些参数;
镜像构建:调用”docker build –t 内部Docker仓库/产品线名/应用名:标签 .”进行镜像构建,构建过程中有任务异常,发送失败消息给发布服务;
镜像推送:首先登录内部Docker仓库,登录成功后调用”docker push 内部Docker仓库/产品线名/应用名:标签”进行镜像推送。
发布服务本身比较大一些,包括用户权限管理、需求管理、产品线管理、应用管理、在线文档管理、缺陷管理、变更管理、Kubernetes应用配置管理、发布管理等,在这里主要介绍一下应用管理、Kubernetes应用配置管理、应用发布管理三个模块:
应用管理:应用管理主要维护了应用本身的一些元数据,还一些辅助配置,像编译应用时使用的JDK版本、生成Dockerfile时使用的基本镜像等;
Kubernetes应用配置管理:这部分的配置是和应用直接挂钩的,最终存储下来的是Kubernetes的REST接口能识别的一个对象JSON串。这部分主要维护了Kubernetes的Deployment、Service、HorizontalPodAutoscaler、Scale四个对象,对Service、HorizontalPodAutoscaler两个对象都添加了启用、停用开关,Service对象默认打开,而HPA对象默认关闭,我们使用Scale对象主要用于对某些应用进行手工扩缩容,来解决一些特殊场景的需求;图示如下:
3. 应用发布管理:每次发布是针对某个应用的代码分支进行发布,首先由构建服务将对应分支的镜像构建并推送完成,对应应用还要提前做Kubernetes相关的配置,然后调用我们进行一次薄封装后的Kubernetes的REST接口:pulish(k8s-master:port,Kubernetes应用配置生成的JSON串)进行应用的发布,发布之后还要进行发布状态的监控,要让每个应用的应用负责人清楚看到这次发布的结果。
改造过程中的问题
在这次改造过程遇到了很多千奇百怪的问题,在这里介绍几个印象比较深的问题及解决办法:
在每次重新发布,更新Kubernetes的Service对象时,报无法更新的错误,查官方文档获知更新Service对象时,还要通过GET请求获取resourceVersion、clusterIP两个值put进来才可以;
在使用Logstash将采集的日志输出到HDFS时,开始使用WebHDFS插件,配置是完全按照官方文档的配置,可是一直报一个Error错误,最后我们改用HttpFS插件解决的;
最初构建服务使用JDK7进行编译打包的,后来发现别的很多应用是在JDK 8下开发的,所以这就要求构建服务要能提供JDK多版本编译环境,我们当时是用Maven的toolchains插件解决的;
在Kubernetes环境,有些时候会出现某些pod删除不掉,到现在为止没想到太好的办法,我们暂时是靠到杀掉Node上的进程解决的。
Q:构建镜像用的什么技术?A:我们使用的是Docker自身提供的docker build命令进行镜像构建的。
Q:Config Center能否详细说一下?
A:我们使用Config Center主要来管理应用自身的配置文件,应该在启动前首先拽下自己的配置文件;还有就是对应用进行Leader控制,因为应用可能会跑多个实例的,像定时任务类的功能,在同一个时间点只能其中一个实例生效。
Q: 如何动态生成Dockerfile,如何在Docker镜像里配置JVM参数?
A:Dockerfile文件:我们是使用sh脚本生成的,将内容&&Dockerfile中;JVM参数是在应用中配置的,发送构建消息时,作为消息内容送过去。
Q: Deployment 滚动更新如何设置间隔时间呢?
A:Deployment对象有个minReadySeconds属性就是来解决这个问题的。
Q: Logstash的日志为啥是发送到HDFS而不是ES?有什么考虑么?
A:我们将Logstash采集到的日志输出到两个地方:ES、HDFS,输出到ES直接在Kibana上搜索到,而输出到HDFS便于在Kibana上面将日志文件进行下载下来。
Q:Docker Registry的在garbage collect时怎么保证高可用?
A:我们使用的由VMware公司中国团队开源的Harbor,无论安全、效率、可用性方面都提供了很强的保障了。
Q:kubectl set image的时候 能不能限制 每变更一个容器,再保证http访问正常的前提下,再变更下一个容器 。毕竟有的服务启动时间超过30秒,对外的服务忍受不了信么久的不可访问时间的?
A:这个可以直接借助Kubernetes的RollingUpdate功能就可以做了。
Q:Dockerfile动态生成怎么做的,用的是什么生成工具?
A:使用sh脚本生成,将内容&&Dockerfile中去可以了。
Q:统一的流程给实际的生产带来了怎样的好处能否介绍一下?
A:流程统一后,像源码管理、日志采集及搜索、应用发布,应用滚动升级等就不需要应用本身来管了,这样各业务系统本身就会更加专注于自身的业务功能了。用Docker部署一个Web应用有问题,上知乎。知乎作为中文互联网最大的知识分享平台,以「知识连接一切」为愿景,致力于构建一个人人都可以便捷接入的知识分享网络,让人们便捷地与世界分享知识、经验和见解,发现更大的世界。本文将以个人(开发)的角度,讲述如何使用Docker技术在线上单机模式下部署一个Web应用,如有错误欢迎指出。上次在提到了Docker,这次打算把这个坑展开来讲。首先,什么是Docker?根据官网描述,我们可以得知,是一个软件/容器平台,使用了虚拟化技术(cgroups,namespaces)来实现操作系统的资源隔离和限制,对于开发人员来说,容器技术为应用的部署提供了沙盒环境,我们可以在独立的容器运行和管理应用程序进程,Docker提供的抽象层使得开发人员之间可以保持开发环境相对的一致,避免了冲突。下面体验下Docker的使用:使用下面的shell命令安装Docker$ curl -sSL https://get.docker.com/ | sh
安装成功后,使用下面的命令应该能显示Docker的版本信息,说明Docker已经被安装了$ docker -v
Docker version 17.04.0-ce, build 4845c56
接着我们使用Docker创建一个nginx的容器:$ docker run -d --name=web -p 80:80 nginx:latest
这条命令表示Docker基于nginx:alpine这个Docker镜像,创建一个名称为web的容器,并把容器内部的80端口与宿主机上的80端口做映射,使得通过宿主机80端口的流量转发到容器内部的80端口上。使用docker ps命令,可以列出正在运行的容器,可以看到,刚才基于nginx镜像创建的容器已经处于运行状态了:$ docker ps
CONTAINER ID
nginx:latest
"nginx -g 'daemon ..."
8 minutes ago
Up 8 minutes
0.0.0.0:80-&80/tcp, 443/tcp
现在访问宿主机地址的80端口,看到nginx的欢迎页面。Docker容器本质上是一个运行的进程以及它需要的一些依赖,而Docker镜像则是定义这个容器的一个"模版"。使用docker images能看到目前的镜像:$ docker images
REPOSITORY
bedece1f06cc
10 minutes ago
了解到这个事实之后,我们使用下面的命令进入刚才创建的容器内部$ docker exec -i -t web bash
现在处于的是容器内部的根文件系统(rootfs),它跟宿主机以及其他容器的环境是隔离开的,看起来这个容器就是一个独立的操作系统环境一样。使用ps命令可以看到容器内正在运行的进程:$ ps -l
0:00 nginx: master process nginx -g daemon off;
0:00 nginx: worker process
0:00 ps -l
使用exit命令可以从容器中退出,回到宿主机的环境:$ exit
使用docker inspect命令我们可以看到关于这个容器的更多详细信息:$ docker inspect web
结果是用json格式表示的容器相关信息,拉到下面的Networks一列可以看到这个容器的网络环境信息:"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "db3eef8257dae57f4efb5ce265ccb4b3e5",
"EndpointID": "e3ab409f152e8e32ceaf3bba8cc99de4",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02"
内容显示了这个容器使用了bridge桥接的方式通信,它是docker容器默认使用的网络驱动(使用docker network ls可以看到所有的驱动),从上面可以看到这个容器的IP地址为172.17.0.2,网关地址为172.17.0.1。现在回想刚才的例子,访问宿主机的80端口,宿主机是怎么跟容器打交道,实现转发通信的呢?要解决这个问题,我们首先要知道,docker在启动的时候会在宿主机上创建一块名为docker0的网卡,可以用ifconfig查看:$ ifconfig
Link encap:Ethernet
HWaddr 02:42:a4:e4:10:80
inet addr:172.17.0.1
Bcast:0.0.0.0
Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST
RX packets:1414 errors:0 dropped:0 overruns:0 frame:0
TX packets:1778 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:181802 (181.8 KB)
TX bytes:142440 (142.4 KB)
这个网卡的ip地址为172.17.0.1,看到这里你是否想起了刚才我们创建的容器使用的网关地址即为172.17.0.1?我们是否可以大胆地猜测,docker容器就是通过这张名为docker0的网卡进行通信呢?确实如此,以单机环境为例,Docker Daemon启动时会创建一块名为docker0的虚拟网卡,在Docker初始化时系统会分配一个IP地址绑定在这个网卡上,docker0的角色就是一个宿主机与容器间的网桥,作为一个二层交换机,负责数据包的转发。当使用docker创建一个容器时,如果使用了bridge模式,docker会创建一个vet对,一端绑定到docker0上,而另一端则作为容器的eth0虚拟网卡。使用ifconfig也可以看到这个veth对的存在:veth8231e5b Link encap:Ethernet
HWaddr 16:e8:f2:1d:e1:4d
UP BROADCAST RUNNING MULTICAST
RX packets:29 errors:0 dropped:0 overruns:0 frame:0
TX packets:29 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:4033 (4.0 KB)
TX bytes:3741 (3.7 KB)
我找了一张图,可以很好地表示veth对的存在方式:而真正实现端口转发的魔法的是nat规则。如果容器使用-p指定映射的端口时,docker会通过iptables创建一条nat规则,把宿主机打到映射端口的数据包通过转发到docker0的网关,docker0再通过广播找到对应ip的目标容器,把数据包转发到容器的端口上。反过来,如果docker要跟外部的网络进行通信,也是通过docker0和iptables的nat进行转发,再由宿主机的物理网卡进行处理,使得外部可以不知道容器的存在。使用iptables -t nat命令可以看到添加的nat规则:$ iptables -t nat -xvL
Chain PREROUTING (policy ACCEPT 376 packets, 21292 bytes)
bytes target
prot opt in
destination
4609864 DOCKER
ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 376 packets, 21292 bytes)
bytes target
prot opt in
destination
Chain OUTPUT (policy ACCEPT 286 packets, 21190 bytes)
bytes target
prot opt in
destination
!127.0.0.0/8
ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 290 packets, 21430 bytes)
bytes target
prot opt in
destination
0 MASQUERADE
172.17.0.0/16
0 MASQUERADE
172.17.0.2
172.17.0.2
tcp dpt:http
Chain DOCKER (2 references)
bytes target
prot opt in
destination
docker0 any
!docker0 any
tcp dpt:http to:172.17.0.2:80
从上面的最后一行可以观察到流量转发到了172.17.0.2的80端口上,这个地址就是刚才创建容器使用的IP地址。现在知道在刚才的例子中宿主机是怎么跟容器通信了吧,那么容器跟容器之间通信呢?类似地,也是通过这个docker0交换机进行广播和转发。扯的有点多,开始进入正题,先写一个Web应用压压惊。一般情况下,如果你要编写一个Web项目,你会做什么呢?反正对于我来说,如果我要写一个python web项目的话,我会先用virtualenv建立一个隔离环境,进入环境内,使用pip安装Django,最后用django-admin startproject创建一个项目,搞定。但是如果用容器化的方式思考,我们大可直接借助于容器的隔离性优势,更好地控制环境和版本的隔离,通常情况下你都不需要再关心用pyenv,virtualenv这种方式来初始化python环境的了,一切交给docker来完成吧。甚至把安装django这个步骤也省了,直接通过一句命令来拉取一个安装了django的Python环境的镜像。$ docker pull django
现在通过这个镜像运行django容器,同时进入容器Shell环境:$ docker run -it --name=app -p
django bash
在/usr/src这个目录下新建一个app目录,然后用django-admin命令新建一个django项目:$ cd /usr/src
$ mkdir app
$ django-admin startproject django_app
然后使用下面的命令,在容器8000端口上运行这个应用:$ python manage.py makemigartions
$ python manage.py migrate
$ python manage.py runserver 0.0.0.0:8000 &
由于之前已经将容器的8000端口与宿主机的8080端口做了映射,因此我们可以通过访问宿主机的8080端口访问这个应用。$ exit #退出容器
$ curl -L http://127.0.0.1:8080/
注意了,对这个容器的所有修改仅仅只对这个容器有效,不会影响到镜像和基于镜像创建的其他容器,当这个容器被销毁之后,所做的修改也就随之销毁。下面新建一个应用ping,作用是统计该应用的访问次数,每次访问页面将次数累加1,返回响应次数给前端页面,并把访问次数存到数据库中。使用redis作为ping的数据库,与之前类似,拉取redis的镜像,运行容器。$ docker pull redis
$ docker run -d --name=redis -p 6379 redis
由于django容器需要与redis容器通信的话首先要知道它的ip地址,但是像刚才那样,每次都手工获取容器的ip地址显然是一件繁琐的事情,于是我们需要修改容器的启动方式,加入—link参数,建立django容器与redis容器之间的联系。删除掉之前的容器,现在重新修改django容器的启动方式:$ docker run -it --name=app -p
-v /code:/usr/src/app --link=redis:db django bash
这次加入了两个参数:-v /code:/usr/src/app 表示把宿主机上的/code目录挂载到容器内的/usr/src/app目录,可以通过直接管理宿主机上的挂载目录来管理容器内部的挂载目录。--link=redis:db 表示把redis容器以db别名与该容器建立关系,在该容器内以db作为主机名表示了redis容器的主机地址。现在进入到django容器,通过ping命令确认django容器能访问到redis容器:$ ping db
PING db (192.168.32.12): 56 data bytes
64 bytes from 192.168.32.12: icmp_seq=0 ttl=64 time=0.463 ms
64 bytes from 192.168.32.12: icmp_seq=1 ttl=64 time=0.086 ms
像之前一样,建立一个项目,接着使用django-admin新建一个应用:$ django-admin startapp ping
编写ping的视图,添加到项目的urls.py:from django.shortcuts import render
from django.http import HttpResponse
import redis
rds = redis.StrictRedis('db', 6379)
def ping(request):
rds.incr('count', 1)
cnt = rds.get('count')
cnt = b'0' if cnt is None else cnt
return HttpResponse(cnt.decode())
''' urls.py
from pingtest.views import ping
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', ping)
别忘了安装redis的python驱动:$ pip install redis
运行django应用,访问应用的根地址,如无意外便能看到随着页面刷新累加的数字。$ curl http://127.0.0.1/
$ curl http://127.0.0.1/
你或许会想,每次创建一个容器都要手工做这么多操作,好麻烦,有没有更方便的方式地来构建容器,不需要做那么多额外的环境和依赖安装呢?仔细一想,其实我们创建的容器都是建立在基础镜像上的,那么有没有办法,把修改好的容器作为基础镜像,以后需要创建容器的时候都使用这个新的镜像呢?当然可以,使用docker commit [CONTAINER]的方式可以将改动的容器导出为一个Docker镜像。当然,更灵活的方式是编写一个Dockerfile来构建镜像,正如Docker镜像是定义Docker容器的模版,Dockerfile则是定义Docker镜像的文件。下面我们来编写一个Dockerfile,以定义出刚才我们进行改动后的容器导出的镜像。下面加入supervisor和gunicorn以更好地监控和部署应用进程:gunicorn的配置文件:import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'meinheld.gmeinheld.MeinheldWorker'
user = 'root'
loglevel = 'warning'
reload = True
accesslog = '-' #to supervisord's stdout
supervisord的配置文件:[supervisord]
nodaemon=true
logfile_maxbytes=10MB
loglevel=debug
[program:ping]
command=gunicorn -c /etc/gunicorn_conf.py django_app.wsgi:application
directory=/usr/src/app
process_name=root
numprocs=1
autostart=true
autorestart=true
redirect_stderr=True
以supervisord作为web应用容器的启动进程,supervisord来管理gunicorn的进程。这里说明一下的是,由于使用docker logs命令来打印容器的日志时默认是从启动进程(supervisord)的stdout和stderr里收集的,而gunicorn又作为supervisord的派生进程存在,因此要正确配置gunicorn和supervisord的日志选项,才能从docker logs中看到有用的信息。把上面所做的修改混杂在一起,终于得出了第一个Dockerfile:FROM django:latest
COPY ./app /usr/src/app
COPY supervisord.conf /etc/supervisord.conf
COPY gunicorn_conf.py /etc/gunicorn_conf.py
RUN apt-get update && \
apt-get install -y supervisor && \
rm -rf /var/lib/apt/lists/*
RUN pip install meinheld && \
pip install gunicorn && \
cd /usr/src/app && \
pip install -r requirement.txt && \
python manage.py makemigrations && \
python manage.py migrate
WORKDIR /usr/src/app
CMD supervisord -c /etc/supervisord.conf
上面的Dockerfile的说明如下:FROM指令制定了该镜像的基础镜像为django:latest。三行COPY指令分别将宿主机的代码文件和配置文件复制到容器环境的对应位置。接着两行RUN指令,一条指令安装supervisor,另一条指令安装python的依赖以及初始化django应用。最后运行supervisord,配置为刚才复制的supervisor的配置文件。上面每一条指令都会由docker容器执行然后提交为一个镜像,叠在原来的镜像层的上方,最后得到一个拥有许多镜像层叠加的最终镜像。完成Dockerfile的编写后,只需要用docker build命令就能构建出一个新的镜像:docker build -t test/app .
接着就可以根据这个镜像来创建和运行容器了:$ docker run -d --name=app -p
-v /code:/usr/src/app --link=redis:db test/app
目前为止,项目的应用结构图如下:现在,如果Redis这个节点出现故障的话会怎么样?答案是,整个服务都会不可用了,更糟糕的是,数据备份和恢复同步成为了更棘手的问题。很明显,我们不能只依赖一个节点,还要通过建立主从节点防止数据的丢失。再创建两个redis容器,通过slaveof指令为Redis建立两个副本。$ docker run -d --name=redis_slave_1 -p
--link=redis:master redis redis-server --slaveof master 6379
$ docker run -d --name=redis_slave_2 -p
--link=redis:master redis redis-server --slaveof master 6379
现在写入到Redis主节点的数据都会在从节点上备份一份数据。现在看起来好多了,然而当Redis master挂掉之后,服务仍然会变的不可用,所以当master宕机时还需要通过选举的方式把新的master节点推上去(故障迁移),Redis Sentinel正是一个合适的方式,我们建立Sentinel集群来监控Redis master节点,当master节点不可用了,再由Sentinel集群根据投票选举出slave节点作为新的master。下面为Sentinel编写Dockerfile,在redis镜像的基础上作改动:FROM redis:latest
COPY run-sentinel.sh /run-sentinel.sh
COPY sentinel.conf /etc/sentinel.conf
RUN chmod +x /run-sentinel.sh
ENTRYPOINT ["/run-sentinel.sh"]
Sentinel的配置文件:port 26379
sentinel monitor master redis-master 6379 2
sentinel down-after-milliseconds master 30000
sentinel parallel-syncs master 1
sentinel failover-timeout master 180000
run-sentinel.sh:#!/bin/bash
exec redis-server /etc/sentinel.conf --sentinel
构建出Sentinel的镜像文件,容器运行的方式类似于redis:$ docker run -d --name=sentinel_1 --link=redis:redis-master [build_sentinel_image]
$ docker run -d --name=sentinel_2 --link=redis:redis-master [build_sentinel_image]
$ docker run -d --name=sentinel_3 --link=redis:redis-master [build_sentinel_image]
这下Sentinel的容器也搭建起来了,应用的结构图如下:简单验证一下当redis主节点挂掉后sentinel怎么处理:$ docker pause redis-master
$ docker logs -f --tail=100 sentinel_1
1:X 17 Apr 14:32:51.633 # +sdown master master 192.168.32.12 6379
1:X 17 Apr 14:32:52.006 # +new-epoch 1
1:X 17 Apr 14:32:52.007 # +vote-for-leader 35ff9ecbea1863cae 1
1:X 17 Apr 14:32:52.711 # +odown master master 192.168.32.12 6379 #quorum 3/2
1:X 17 Apr 14:32:52.711 # Next failover delay: I will not start a failover before Mon Apr 17 14:33:02 2017
1:X 17 Apr 14:32:53.221 # +config-update-from sentinel 35ff9ecbea1863cae 192.168.32.7 26379 @ master 192.168.32.12 6379
1:X 17 Apr 14:32:53.221 # +switch-master master 192.168.32.12 .32.5 6379
1:X 17 Apr 14:32:53.221 * +slave slave 192.168.32.6:.32.6 6379 @ master 192.168.32.5 6379
1:X 17 Apr 14:32:53.221 * +slave slave 192.168.32.12:.32.12 6379 @ master 192.168.32.5 6379
$ docker exec -it sentinel_1 redis-cli -p 26379 info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=master,status=ok,address=192.168.32.5:6379,slaves=2,sentinels=3
修改代码用Sentinel获取redis实例:sentinel = Sentinel([('sentinel', 26379)])
rds = sentinel.master_for('master')
下面再来考虑这种情况:假设我们对django_app容器进行伸缩,扩展出三个一模一样的django应用容器,这时候怎么办,该访问哪个?显然,这时候需要一个负载均衡的工具作为web应用的前端,做反向代理。nginx是一个非常流行的web服务器,用它完成这个当然没问题,这里不说了。下面说一说个人尝试过的两种选择:LVS(Linux Virtual Server)作为最外层的服务,负责对系统到来的请求做负载均衡,转发到后端的服务器(Real Server)上,DR(Direct Route)算法是指对请求报文的数据链路层进行修改mac地址的方式,转发到后端的一台服务器上,后端的服务器集群只需要配置和负载均衡服务器一样的虚拟IP(VIP),请求就会落到对应mac地址的服务器上,跟NAT模式相比,DR模式不需要修改目的IP地址,因此在返回响应时,服务器可以直接将报文发送给客户端,而无须转发回负载均衡服务器,因此这种模式也叫做三角传输模式。Haproxy是一个基于TCP/HTTP的负载均衡工具,在负载均衡上有许多精细的控制。下面简单地使用Haproxy来完成上面的负载均衡和转发。首先把haproxy的官方镜像下载下来:$ docker pull haproxy
这类的镜像的Dockerfile都可以在Docker Hub上找到。这次同样选择编写Dockerfile的方式构建自定的haproxy镜像:FROM haproxy:latest
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
暂时只需要把配置文件复制到配置目录就可以了,因为通过看haproxy的Dockerfile可以看到最后有这么一行,于是乎偷个懒~CMD ["haproxy", "-f", "/usr/local/etc/haproxy/haproxy.cfg"]
haproxy的配置文件如下:global
log 127.0.0.1 local0
maxconn 4096
log 127.0.0.1 local3
option dontlognull
option redispatch
maxconn 2000
balance roundrobin
timeout connect 5000ms
timeout client 5000ms
timeout server 5000ms
frontend main
bind *:6301
default_backend webserver
backend webserver
server app1 app1:8000 check inter 2000 rise 2 fall 5
server app2 app2:8000 check inter 2000 rise 2 fall 5
server app3 app3:8000 check inter 2000 rise 2 fall 5
这里的app即web应用容器的主机名,运行haproxy容器时用link连接三个web应用容器,绑定到宿主机的80端口。$ docker run -d --name=lb -p 80:6301 --link app1:app1 --link app2:app2 --link app3:app3
[build_haproxy_image]
这时候访问宿主机的80端口后,haproxy就会接管请求,用roundrobin方式轮询代理到后端的三个容器上,实现健康检测和负载均衡。现在又有一个问题了,每次我们想增加或者减少web应用的数量时,都要修改haproxy的配置并重启haproxy,十分的不方便。理想的方式是haproxy能自动检测到后端服务器的运行状况并相应调整配置,好在这种方式不难,我们可以使用etcd作为后端服务器的服务发现工具,把服务器的信息写入到etcd的数据库中,再由confd来间隔一段时间去访问etcd的api,将服务器的信息写入到模版配置中,并更新haproxy的文件以及重启haproxy进程。按官方的说法,etcd是一个可靠的分布式的KV存储系统,而confd则是使用模版和数据管理应用配置的一个工具,关于他俩我还没太多了解,所以不多说,下面把他们集成到上面的应用中。创建一个etcd的容器:docker run -d \
-e CLIENT_URLS=http://0.0.0.0:2379 \
-e PEER_URLS=http://0.0.0.0:2380 \
-v /etc/ssl/certs/:/etc/ssl/certs/ \
elcolio/etcd \
-name etcd \
-initial-cluster-token=etcd-cluster-1 \
-initial-cluster="etcd=http://etcd:2380"\
-initial-cluster-state=new \
-advertise-client-urls=http://etcd:2379 \
-initial-advertise-peer-urls http://etcd:2380
confd的处理比较简单,把confd的二进制文件和配置文件集成到之前haproxy的Dockerfile中:FROM haproxy:latest
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY confd .
RUN chmod +x confd
COPY haproxy.toml /etc/confd/conf.d/
COPY haproxy.tmpl /etc/confd/templates/
COPY boot.sh .
COPY watcher.sh .
CMD ["./boot.sh"]
通过之前haproxy的配置文件创建出新的模版文件,修改backend的配置,加入模版指令,表示confd从etcd的前缀为/app/servers的所有key中获取键值对,作为server的key的value,逐条追加到配置文件中去:backend webserver
{{range gets "/app/servers/*"}}
server {{base .Key}} {{.Value}} check inter 2000 rise 2 fall 5
下面是confd的配置文件:[template]
src = "haproxy.tmpl"
dest = "/usr/local/etc/haproxy/haproxy.cfg"
"/app/servers"
owner = "root"
mode = "0644"
reload_cmd = "kill -HUP 1"
confd会把数据填入上面的模版文件,并把配置更新到haproxy配置的目标路径,再使用reload_cmd指定的命令重启haproxy。修改后的haproxy镜像最后通过boot.sh启动进程:#!/bin/bash
./watcher.sh &
exec /docker-entrypoint.sh haproxy -f /usr/local/etc/haproxy/haproxy.cfg
watcher.sh启动了confd间隔一段时间去访问etcd的地址,检查是否有更新:./confd -interval 10 -node http://etcd:2379 -config-file /etc/confd/conf.d/haproxy.toml
启动haproxy时建立与etcd容器间的连接:$ docker run -d --name=lb -p 80:6301 --link app1:app1 --link app2:app2 --link app3:app3
--link=etcd:etcd [build_haproxy_image]
下面通过调用etcd的api在/app/servers上新建一个服务器节点:$ docker exec -it etcd etcdctl set /app/servers/app1 172.17.0.5:8000
观察haproxy容器的日志,可以看到配置被更新了:$ docker logs -f lb
T16:24:00Z d4 ./confd[7]: INFO Backend set to etcd
&7&haproxy-systemd-wrapper: executing /usr/local/sbin/haproxy -p /run/haproxy.pid -f /usr/local/etc/haproxy/haproxy.cfg -Ds
T16:24:00Z d4 ./confd[7]: INFO Starting confd
T16:24:00Z d4 ./confd[7]: INFO Backend nodes set to http://etcd:2379
T16:24:00Z d4 ./confd[7]: INFO /usr/local/etc/haproxy/haproxy.cfg has md5sum 8e6fc297a13fbbe130cecf4 should be f5e8a4b8fbea0b20da3796334bac1ddb
T16:24:00Z d4 ./confd[7]: INFO Target config /usr/local/etc/haproxy/haproxy.cfg out of sync
T16:24:00Z d4 ./confd[7]: INFO Target config /usr/local/etc/haproxy/haproxy.cfg has been updated
&5&haproxy-systemd-wrapper: re-executing on SIGHUP.
&7&haproxy-systemd-wrapper: executing /usr/local/sbin/haproxy -p /run/haproxy.pid -f /usr/local/etc/haproxy/haproxy.cfg -Ds -sf 15 16 17 18
最终的应用结构图如下:运行在机器上的服务时刻有可能有意外发生,因此我们需要一个服务来监控机器的运行情况和容器的资源占用。netdata是服务器的一个实时监测工具,利用它可以直观简洁地了解到服务器的运行情况。当docker镜像和容器数量增多的情况下,手工去运行和定义docker容器以及其相关依赖无疑是非常繁琐和易错的工作。Docker Compose是由Docker官方提供的一个容器编排和部署工具,我们只需要定义好docker容器的配置文件,用compose的一条命令即可自动分析出容器的启动顺序和依赖,快速的部署和启动容器。下面编写好compose的文件:version: '2.1'
container_name: lb
build: ./builds/haproxy
restart: always
- ping-app
- etcd:etcd
- netdata:netdata
restart: always
- ./app:/usr/src/app
- sentinel
- redis-master:db
redis-master:
image: redis:latest
restart: always
redis-slave:
image: redis:latest
command: redis-server --slaveof master 6379
restart: always
- redis-master:master
build: ./builds/sentinel
restart: always
- redis-master:redis-master
- redis-slave
image: elcolio/etcd
command: -name etcd -initial-cluster-token=etcd-cluster-1 -initial-cluster="etcd=http://etcd:2380" -initial-cluster-state=new -advertise-client-urls=http://etcd:2379 -initial-advertise-peer-urls http://etcd:2380
environment:
- CLIENT_URLS=http://0.0.0.0:2379
- PEER_URLS=http://0.0.0.0:2380
- /etc/ssl/certs/:/etc/ssl/certs/
image: titpetric/netdata
restart: always
- SYS_PTRACE
- /proc:/host/proc:ro
- /sys:/host/sys:ro
只需几条命令,就能启动和伸缩容器:docker-compose -f docker-compose.yml up -d
docker-compose -f docker-compose.yml scale redis-slave=2
docker-compose -f docker-compose.yml scale sentinel=3
docker-compose -f docker-compose.yml scale ping-app=3
再通过一个脚本把web应用注册到etcd中去:APP_SERVERS=$(docker-compose -f docker-compose.yml ps ping-app | awk '{print $1}' |sed '1,2d'|xargs docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' | xargs)
APP_SERVER_PORT=8000
for ETCD_NODE in ${APP_SERVERS//\s/};
docker-compose -f docker-compose.yml exec etcd etcdctl set /app/servers/app$INDEX $ETCD_NODE:$APP_SERVER_PORT
INDEX=$(expr $INDEX + 1)
就这样,一个基于Docker构建并具有良好可用性的web应用就完成了。232分享收藏文章被以下专栏收录}

我要回帖

更多关于 docker已有容器增加ip 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信