2020-12-04 Dockerfile打包镜像方法、ubuntu service机制

昨天在通过Dockerfile打包镜像时,碰到自启动的问题,一直搞到凌晨都没能解决,到了子时心里就开始发慌,心慌的时候是锻炼心性最好的时候,就像肌肉酸胀时是最锻炼肌肉的临界区。今早起床后头脑清醒了,重新梳理了下思路,很快就把问题解决了。特记录于此,希望有缘人能避免在这条弯路上耗费太多时间。
翻看了下我昨天打包的镜像,从v1.0到v11.0,一共做了11个版本,其实最后已经在球门前徘徊了,就差临门一脚,这一脚是早上的v12.0。是不是想起了小学语文课本里有个做板凳的故事。那是谁的三个板凳来着?但愿有朝一日也能有人记得我的12个镜像。其实不是那三个板凳让当事人留芳,而是希望当事人让这12个镜像能至少留痕。


1.png

此篇为补昨天的日志。本来昨天已经有了个开头,规划了一下未来几周的探索路线。结果刚出门就堵车了,今天就从昨日的开头续写吧。

有了集群加持,野心就变大了,想在本系列运维日志中实现一个中等规模的分布式网站架构。

  • 里程碑1.0:一个django+uwsgi的api后端、一个mysql数据库后台、一个vue+nginx前端。
  • 里程碑2.0:多前端+入口负载均衡(共享存储、反向代理自定义负载均衡策略)
  • 里程碑3.0:多后端+内部负载均衡(搭建内部DNS,规范接口,DNS轮询)
  • 里程碑4.0:分布式存储
  • 里程碑5.0:分布式数据库
  • 里程碑6.0:非关系数据库
  • 里程碑7.0:大数据分析

这是一个5周滚动计划,随着计划的推进,后面的里程碑会进行修正与细化。这条探索路径侧重软硬件部署的研究,即纯技术探索。对于最能体现工程师和架构师水平的业务拆分,这里不做探索,毕竟后者包含更多艺术成分和具体业务环境下的权衡。(p.s.我猜曹雪芹当年写红楼也是这么个思路,先把结论写出来,后面再来铺排。)

里程碑1.0

一个django+uwsgi的api后端、一个mysql数据库后台、一个vue+nginx前端

上周是在本地进行django的uwsgi部署,这次上容器。首先拉取一个最基础的ubuntu镜像,基于这个镜像安装uwsgi和部署django。
运行ubuntu镜像时要以-it模式(交互模式)运行,使容器始终保持与标准输入的连接。因为ubuntu镜像是最原始的基础镜像,里面没有跑任何后台守护进程,若不给bash指定一个始终连接的标准输入,容器运行后就会立刻结束。指令如下:
docker run --name mydjango -d -p 82:80 -it ubuntu:latest bash
通过exec指令获取mydjango容器的bash:
docker exec -it mydjango bash
因为需要安装python、pip、uwsgi,先要更新apt源。查看一下这个容器中的系统版本:
cat /etc/issue
返回的结果是20.04版,和宿主机的版本一致,可以直接用本地的sources.list覆盖容器中的源(如果版本不一致,可查看我11月27日的日志),下面这段指令是在宿主机上执行:
docker cp /etc/apt/sources.list mydjango:/etc/apt/sources.list
返回容器中,更新源。问题开始出现了,虽然系统的版本号一致,但docker容器里这个裁剪版的ubuntu和linux发行版的内核是有差别的,apt几乎瘫痪。要想安装python,只能通过编译安装这一条路。遂到python官网下载了源码,解压拷贝到容器中,然而在容器里gcc编译器也是没有的,这条路似乎绕得有点远。何不直接从已配置了python的镜像起步呢?rackspacedot/python37是docker hub上排名第一的基础镜像包,几十万的下载量,看来需求量还是很大,就从它开始吧。事后我在想,花些时间把编译安装这条路走通,也可以到docker hub上发布配置了python最新版的基础镜像包吧。这就像基础科学和工程科学的区别,世界上总存在对基础感兴趣又耐得住寂寞的人,有兴趣就不寂寞。
python基础镜像拉取到本地,同样的方法运行后获取容器bash。我在django开发环境下是配置了虚拟环境的,尝试了直接将虚拟环境整体拷贝到容器中运行,发现python版本问题,虚拟环境(与开发环境一致)是3.8.5,而容器里的python是较早的版本。转念一想,既然都已经是容器了,跑的是单个应用,需要虚拟环境干嘛?直接把venv目录删除,在开发环境下通过pip freeze > requirements.txt生成项目依赖清单,到容器里pip install -r requirements.txt将依赖包一键安装到主环境中。不知什么原因,在安装uwsgi时出现了问题,感谢这个问题,让我又尝试并找到了配置镜像的正道。这个问题事后回想,可能只是网络的偶发故障导致的,实际上后来通过Dockefile编译镜像包时,安装uwsgi的内部指令其实是一样的,但却并没有再出现问题。难道是上帝关上那道门为的是打开这道大门?Thanks God。
刚才提到了Dockerfile,这个才是编译镜像包的正确姿势。
在制作自己的镜像时,首先准备好需要复制到镜像中的文件,包括程序文件和配置文件。再通过Dockerfile中的指令进行文件复制、程序运行和启动配置。其实你想想,你通过镜像生成一个容器后,如果还需要对容器进行调整,无非也就是复制一些文件进去,覆盖一些配置文件,运行一些程序。而这些工作,都可以通过Dockerfile脚本的形式完成。这样生成的镜像运行后就无需再调整。这个工作有点类似于制作操作系统安装包,前天还和一个老朋友探讨过操作系统安装包的制作,今天就体会到了,虽然只是容器环境下,复杂度不同,但感觉应该类似。不能如愚见指月,观指不观月。
Dockefile的主体包括如下几个部分:

FROM rackspacedot/python37:latest #指明基础镜像包
COPY xxx /xxx/ #将Dockerfile同目录下的xxx文件或目录拷贝到生成镜像中的/xxx/目录下
RUN xxx #这里相当于在bash里执行指令,每条指令用一个RUN来标记,完成service文件的chmod修改等
MAINTAINER name email #留下你的大名和邮箱
EXPOSE 10000 #暴露端口号
ENTRYPOINT xxx #这个是启动执行命令,只能有一条
CMD xxx #这个也是启动执行命令(或给ENTRYPOINT传递默认参数),若启动容器时附加了参数,则CMD中的命令会被忽略

无非就是这些,靠它们的组合,可以配置出充满想象力的镜像。
比如,我在开发环境下的django项目根目录的上层目录处新建了Dockerfile(要细品,别被绕晕了,公益活动就不配图了),因为这个项目根目录要整体拷贝进镜像包中,所以需要与Dockerfile在同一目录下。我还想在镜像中增加service文件,并通过service启动uwsgi。那这个service文件也放到此处,通过COPY指令拷贝到镜像中的/etc/init.d/目录下。关于linux的service,待会儿做个简单介绍,这也算是在绕弯路时收获的风景吧。
万事俱备,只需要在ENTRYPOINT处启动服务就可以了。当然,这个地方也只相当于执行了一条bash指令,如果这条指令不能持续运行,那容器运行后也会随着这条指令的结束而结束。要想让容器持续运行,这条ENTRYPOINT指令不能运行为后台服务。也有变通的方法,就是在CMD里执行一条类似于这样的指令tail -f xxx文件,这条tail -f指令是持续刷新显示xxx文件的内容,类似于一个死循环,这样容器也不会自动停止。但这样做不太优雅。最优雅的方式还是在ENTRYPOINT处,既然是用容器提供一个一直运行的服务,那就在ENTRYPOINT的指令里直接运行这个提供服务的不会停止的程序就可以了。
说一下ubuntu的service机制。我们在bash里运行service xxx start|stop|reload|status指令,其实都是调用了ubuntu服务文件目录下对应文件中的start|stop|reload|status函数。这个服务文件就是一段sh脚本,你也可以增加一些别致的函数,仅此而已。ubuntu的服务目录有很多级别,代表了系统加载过程中的不同阶段。从rc0.d-rc6.d和rcS.d,以及上文提到的init.d。关于它们之间的区别,网上有资料,我们只需要了解在这些目录下可以编写自己的service文件,service文件是一段sh脚本,里面需要提供start、stop、reload、status等函数,当然也可以只提供部分。这些目录里有了service文件,在bash中就可以通过service xxx start来启动服务,xxx服务名就是放进服务目录的service文件名。一个典型的service文件结构如下:

start() 
{
    uwsgi --ini /home/uwsgi.ini & #这是一段bash指令,后面加上&是后台执行的意思,不输出内容到屏幕
    exit 0;
}

stop() 
{
   uwsgi --stop /home/uwsgi_pid.log #同样是一段bash指令,uwsgi可以通过pid的方式优雅停止,这里也给配置上,前提是在uwsgi启动时的配置文件中要指定生成这个pid文件:pidfile = /home/uwsgi_pid.log
}

case "$1" in #$1是service xxx指令所带的第一个参数,比如start或stop
start)
    start
    ;;

stop)
    stop
    ;;

restart)
    stop
    start
    ;;

*)
    echo "Usage: $0 {start|stop|restart}"
    exit 0
    ;;

esac #shell的语法,case esac
exit 0

在构建镜像时,提前准备好这个service文件,就以uwsgiservice命名吧,通过COPY指令拷贝到镜像中的服务目录下(/etc/init.d/uwsgiservice)。我在这个service程序中调用了uwsgi指令,这就需要在service uwsgiservice start时uwsgi已经安装好,即通过RUN指令pip install -r requirements.txt,uwsgi模块已经在 requirements.txt中列出了。
实际配置我的v12.0镜像时,并没有使用服务。还记得前面说过,要想容器运行后不停止,最优雅的方式是在ENTRYPOINT处执行一个不会停止的程序,这个uwsgi就不会停止,直接ENTRYPOINT uwsgi --ini /home/uwsgi.ini就可以了。
配置好Dockerfile,就可以通过docker build -t xxx:tag .指令来制作镜像。这个Dockerfile的名字不要随便取,就叫Dockerfile,位置放到需要拷贝进镜像的文件和目录的同一目录下。-t xxx:tag指定镜像的名字和版本,细心看一下,最后还有一个点,表示当前目录,这意味着执行这条指令时,需要先切换到Dockerfile所在的目录下。当然也可以通过加-f指令来切换目录,但我是本着能少用一个参数就少用一个的原则。
镜像制作好后,docker images看一下,是不是已经列出来了。我打包进镜像的django只有一个功能,就是返回托管django项目的节点的IP地址。因为我想测试集群部署后的负载均衡,当任务被调度到不同的节点后,返回的IP地址是会不同的。python获取主机IP地址的方法有很多,我这里也给出一个最优雅的方式:

import socket
def get_host_ip():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    finally:
        s.close()

    return ip

要想看懂这些内容,恐怕得需要有django的基础知识。不过你如果知道web框架那也好办,django就是一个python下的web框架,类似于php的thinkphp,java的struts等。
这第一个里程碑走到这里才走了三分之一,路漫漫其修远亦,想得到和办得到差距还是挺大的,一个筋斗云十万八千里,那是心的速度,实际的取经路却是八戒离开高老庄的那句话:此去千山万水,路途遥远,容我去与红尘俗世道个别。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容