2020-12-04 Dockerfile打包鏡像方法、ubuntu service機(jī)制

昨天在通過Dockerfile打包鏡像時(shí),碰到自啟動(dòng)的問題,一直搞到凌晨都沒能解決,到了子時(shí)心里就開始發(fā)慌,心慌的時(shí)候是鍛煉心性最好的時(shí)候,就像肌肉酸脹時(shí)是最鍛煉肌肉的臨界區(qū)。今早起床后頭腦清醒了,重新梳理了下思路,很快就把問題解決了。特記錄于此,希望有緣人能避免在這條彎路上耗費(fèi)太多時(shí)間。
翻看了下我昨天打包的鏡像,從v1.0到v11.0,一共做了11個(gè)版本,其實(shí)最后已經(jīng)在球門前徘徊了,就差臨門一腳,這一腳是早上的v12.0。是不是想起了小學(xué)語文課本里有個(gè)做板凳的故事。那是誰的三個(gè)板凳來著?但愿有朝一日也能有人記得我的12個(gè)鏡像。其實(shí)不是那三個(gè)板凳讓當(dāng)事人留芳,而是希望當(dāng)事人讓這12個(gè)鏡像能至少留痕。


1.png

此篇為補(bǔ)昨天的日志。本來昨天已經(jīng)有了個(gè)開頭,規(guī)劃了一下未來幾周的探索路線。結(jié)果剛出門就堵車了,今天就從昨日的開頭續(xù)寫吧。

有了集群加持,野心就變大了,想在本系列運(yùn)維日志中實(shí)現(xiàn)一個(gè)中等規(guī)模的分布式網(wǎng)站架構(gòu)。

  • 里程碑1.0:一個(gè)django+uwsgi的api后端、一個(gè)mysql數(shù)據(jù)庫后臺(tái)、一個(gè)vue+nginx前端。
  • 里程碑2.0:多前端+入口負(fù)載均衡(共享存儲(chǔ)、反向代理自定義負(fù)載均衡策略)
  • 里程碑3.0:多后端+內(nèi)部負(fù)載均衡(搭建內(nèi)部DNS,規(guī)范接口,DNS輪詢)
  • 里程碑4.0:分布式存儲(chǔ)
  • 里程碑5.0:分布式數(shù)據(jù)庫
  • 里程碑6.0:非關(guān)系數(shù)據(jù)庫
  • 里程碑7.0:大數(shù)據(jù)分析

這是一個(gè)5周滾動(dòng)計(jì)劃,隨著計(jì)劃的推進(jìn),后面的里程碑會(huì)進(jìn)行修正與細(xì)化。這條探索路徑側(cè)重軟硬件部署的研究,即純技術(shù)探索。對(duì)于最能體現(xiàn)工程師和架構(gòu)師水平的業(yè)務(wù)拆分,這里不做探索,畢竟后者包含更多藝術(shù)成分和具體業(yè)務(wù)環(huán)境下的權(quán)衡。(p.s.我猜曹雪芹當(dāng)年寫紅樓也是這么個(gè)思路,先把結(jié)論寫出來,后面再來鋪排。)

里程碑1.0

一個(gè)django+uwsgi的api后端、一個(gè)mysql數(shù)據(jù)庫后臺(tái)、一個(gè)vue+nginx前端

上周是在本地進(jìn)行django的uwsgi部署,這次上容器。首先拉取一個(gè)最基礎(chǔ)的ubuntu鏡像,基于這個(gè)鏡像安裝uwsgi和部署django。
運(yùn)行ubuntu鏡像時(shí)要以-it模式(交互模式)運(yùn)行,使容器始終保持與標(biāo)準(zhǔn)輸入的連接。因?yàn)閡buntu鏡像是最原始的基礎(chǔ)鏡像,里面沒有跑任何后臺(tái)守護(hù)進(jìn)程,若不給bash指定一個(gè)始終連接的標(biāo)準(zhǔn)輸入,容器運(yùn)行后就會(huì)立刻結(jié)束。指令如下:
docker run --name mydjango -d -p 82:80 -it ubuntu:latest bash
通過exec指令獲取mydjango容器的bash:
docker exec -it mydjango bash
因?yàn)樾枰惭bpython、pip、uwsgi,先要更新apt源。查看一下這個(gè)容器中的系統(tǒng)版本:
cat /etc/issue
返回的結(jié)果是20.04版,和宿主機(jī)的版本一致,可以直接用本地的sources.list覆蓋容器中的源(如果版本不一致,可查看我11月27日的日志),下面這段指令是在宿主機(jī)上執(zhí)行:
docker cp /etc/apt/sources.list mydjango:/etc/apt/sources.list
返回容器中,更新源。問題開始出現(xiàn)了,雖然系統(tǒng)的版本號(hào)一致,但docker容器里這個(gè)裁剪版的ubuntu和linux發(fā)行版的內(nèi)核是有差別的,apt幾乎癱瘓。要想安裝python,只能通過編譯安裝這一條路。遂到python官網(wǎng)下載了源碼,解壓拷貝到容器中,然而在容器里gcc編譯器也是沒有的,這條路似乎繞得有點(diǎn)遠(yuǎn)。何不直接從已配置了python的鏡像起步呢?rackspacedot/python37是docker hub上排名第一的基礎(chǔ)鏡像包,幾十萬的下載量,看來需求量還是很大,就從它開始吧。事后我在想,花些時(shí)間把編譯安裝這條路走通,也可以到docker hub上發(fā)布配置了python最新版的基礎(chǔ)鏡像包吧。這就像基礎(chǔ)科學(xué)和工程科學(xué)的區(qū)別,世界上總存在對(duì)基礎(chǔ)感興趣又耐得住寂寞的人,有興趣就不寂寞。
python基礎(chǔ)鏡像拉取到本地,同樣的方法運(yùn)行后獲取容器bash。我在django開發(fā)環(huán)境下是配置了虛擬環(huán)境的,嘗試了直接將虛擬環(huán)境整體拷貝到容器中運(yùn)行,發(fā)現(xiàn)python版本問題,虛擬環(huán)境(與開發(fā)環(huán)境一致)是3.8.5,而容器里的python是較早的版本。轉(zhuǎn)念一想,既然都已經(jīng)是容器了,跑的是單個(gè)應(yīng)用,需要虛擬環(huán)境干嘛?直接把venv目錄刪除,在開發(fā)環(huán)境下通過pip freeze > requirements.txt生成項(xiàng)目依賴清單,到容器里pip install -r requirements.txt將依賴包一鍵安裝到主環(huán)境中。不知什么原因,在安裝uwsgi時(shí)出現(xiàn)了問題,感謝這個(gè)問題,讓我又嘗試并找到了配置鏡像的正道。這個(gè)問題事后回想,可能只是網(wǎng)絡(luò)的偶發(fā)故障導(dǎo)致的,實(shí)際上后來通過Dockefile編譯鏡像包時(shí),安裝uwsgi的內(nèi)部指令其實(shí)是一樣的,但卻并沒有再出現(xiàn)問題。難道是上帝關(guān)上那道門為的是打開這道大門?Thanks God。
剛才提到了Dockerfile,這個(gè)才是編譯鏡像包的正確姿勢(shì)。
在制作自己的鏡像時(shí),首先準(zhǔn)備好需要復(fù)制到鏡像中的文件,包括程序文件和配置文件。再通過Dockerfile中的指令進(jìn)行文件復(fù)制、程序運(yùn)行和啟動(dòng)配置。其實(shí)你想想,你通過鏡像生成一個(gè)容器后,如果還需要對(duì)容器進(jìn)行調(diào)整,無非也就是復(fù)制一些文件進(jìn)去,覆蓋一些配置文件,運(yùn)行一些程序。而這些工作,都可以通過Dockerfile腳本的形式完成。這樣生成的鏡像運(yùn)行后就無需再調(diào)整。這個(gè)工作有點(diǎn)類似于制作操作系統(tǒng)安裝包,前天還和一個(gè)老朋友探討過操作系統(tǒng)安裝包的制作,今天就體會(huì)到了,雖然只是容器環(huán)境下,復(fù)雜度不同,但感覺應(yīng)該類似。不能如愚見指月,觀指不觀月。
Dockefile的主體包括如下幾個(gè)部分:

FROM rackspacedot/python37:latest #指明基礎(chǔ)鏡像包
COPY xxx /xxx/ #將Dockerfile同目錄下的xxx文件或目錄拷貝到生成鏡像中的/xxx/目錄下
RUN xxx #這里相當(dāng)于在bash里執(zhí)行指令,每條指令用一個(gè)RUN來標(biāo)記,完成service文件的chmod修改等
MAINTAINER name email #留下你的大名和郵箱
EXPOSE 10000 #暴露端口號(hào)
ENTRYPOINT xxx #這個(gè)是啟動(dòng)執(zhí)行命令,只能有一條
CMD xxx #這個(gè)也是啟動(dòng)執(zhí)行命令(或給ENTRYPOINT傳遞默認(rèn)參數(shù)),若啟動(dòng)容器時(shí)附加了參數(shù),則CMD中的命令會(huì)被忽略

無非就是這些,靠它們的組合,可以配置出充滿想象力的鏡像。
比如,我在開發(fā)環(huán)境下的django項(xiàng)目根目錄的上層目錄處新建了Dockerfile(要細(xì)品,別被繞暈了,公益活動(dòng)就不配圖了),因?yàn)檫@個(gè)項(xiàng)目根目錄要整體拷貝進(jìn)鏡像包中,所以需要與Dockerfile在同一目錄下。我還想在鏡像中增加service文件,并通過service啟動(dòng)uwsgi。那這個(gè)service文件也放到此處,通過COPY指令拷貝到鏡像中的/etc/init.d/目錄下。關(guān)于linux的service,待會(huì)兒做個(gè)簡單介紹,這也算是在繞彎路時(shí)收獲的風(fēng)景吧。
萬事俱備,只需要在ENTRYPOINT處啟動(dòng)服務(wù)就可以了。當(dāng)然,這個(gè)地方也只相當(dāng)于執(zhí)行了一條bash指令,如果這條指令不能持續(xù)運(yùn)行,那容器運(yùn)行后也會(huì)隨著這條指令的結(jié)束而結(jié)束。要想讓容器持續(xù)運(yùn)行,這條ENTRYPOINT指令不能運(yùn)行為后臺(tái)服務(wù)。也有變通的方法,就是在CMD里執(zhí)行一條類似于這樣的指令tail -f xxx文件,這條tail -f指令是持續(xù)刷新顯示xxx文件的內(nèi)容,類似于一個(gè)死循環(huán),這樣容器也不會(huì)自動(dòng)停止。但這樣做不太優(yōu)雅。最優(yōu)雅的方式還是在ENTRYPOINT處,既然是用容器提供一個(gè)一直運(yùn)行的服務(wù),那就在ENTRYPOINT的指令里直接運(yùn)行這個(gè)提供服務(wù)的不會(huì)停止的程序就可以了。
說一下ubuntu的service機(jī)制。我們?cè)赽ash里運(yùn)行service xxx start|stop|reload|status指令,其實(shí)都是調(diào)用了ubuntu服務(wù)文件目錄下對(duì)應(yīng)文件中的start|stop|reload|status函數(shù)。這個(gè)服務(wù)文件就是一段sh腳本,你也可以增加一些別致的函數(shù),僅此而已。ubuntu的服務(wù)目錄有很多級(jí)別,代表了系統(tǒng)加載過程中的不同階段。從rc0.d-rc6.d和rcS.d,以及上文提到的init.d。關(guān)于它們之間的區(qū)別,網(wǎng)上有資料,我們只需要了解在這些目錄下可以編寫自己的service文件,service文件是一段sh腳本,里面需要提供start、stop、reload、status等函數(shù),當(dāng)然也可以只提供部分。這些目錄里有了service文件,在bash中就可以通過service xxx start來啟動(dòng)服務(wù),xxx服務(wù)名就是放進(jìn)服務(wù)目錄的service文件名。一個(gè)典型的service文件結(jié)構(gòu)如下:

start() 
{
    uwsgi --ini /home/uwsgi.ini & #這是一段bash指令,后面加上&是后臺(tái)執(zhí)行的意思,不輸出內(nèi)容到屏幕
    exit 0;
}

stop() 
{
   uwsgi --stop /home/uwsgi_pid.log #同樣是一段bash指令,uwsgi可以通過pid的方式優(yōu)雅停止,這里也給配置上,前提是在uwsgi啟動(dòng)時(shí)的配置文件中要指定生成這個(gè)pid文件:pidfile = /home/uwsgi_pid.log
}

case "$1" in #$1是service xxx指令所帶的第一個(gè)參數(shù),比如start或stop
start)
    start
    ;;

stop)
    stop
    ;;

restart)
    stop
    start
    ;;

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

esac #shell的語法,case esac
exit 0

在構(gòu)建鏡像時(shí),提前準(zhǔn)備好這個(gè)service文件,就以u(píng)wsgiservice命名吧,通過COPY指令拷貝到鏡像中的服務(wù)目錄下(/etc/init.d/uwsgiservice)。我在這個(gè)service程序中調(diào)用了uwsgi指令,這就需要在service uwsgiservice start時(shí)uwsgi已經(jīng)安裝好,即通過RUN指令pip install -r requirements.txt,uwsgi模塊已經(jīng)在 requirements.txt中列出了。
實(shí)際配置我的v12.0鏡像時(shí),并沒有使用服務(wù)。還記得前面說過,要想容器運(yùn)行后不停止,最優(yōu)雅的方式是在ENTRYPOINT處執(zhí)行一個(gè)不會(huì)停止的程序,這個(gè)uwsgi就不會(huì)停止,直接ENTRYPOINT uwsgi --ini /home/uwsgi.ini就可以了。
配置好Dockerfile,就可以通過docker build -t xxx:tag .指令來制作鏡像。這個(gè)Dockerfile的名字不要隨便取,就叫Dockerfile,位置放到需要拷貝進(jìn)鏡像的文件和目錄的同一目錄下。-t xxx:tag指定鏡像的名字和版本,細(xì)心看一下,最后還有一個(gè)點(diǎn),表示當(dāng)前目錄,這意味著執(zhí)行這條指令時(shí),需要先切換到Dockerfile所在的目錄下。當(dāng)然也可以通過加-f指令來切換目錄,但我是本著能少用一個(gè)參數(shù)就少用一個(gè)的原則。
鏡像制作好后,docker images看一下,是不是已經(jīng)列出來了。我打包進(jìn)鏡像的django只有一個(gè)功能,就是返回托管django項(xiàng)目的節(jié)點(diǎn)的IP地址。因?yàn)槲蚁霚y(cè)試集群部署后的負(fù)載均衡,當(dāng)任務(wù)被調(diào)度到不同的節(jié)點(diǎn)后,返回的IP地址是會(huì)不同的。python獲取主機(jī)IP地址的方法有很多,我這里也給出一個(gè)最優(yōu)雅的方式:

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

要想看懂這些內(nèi)容,恐怕得需要有django的基礎(chǔ)知識(shí)。不過你如果知道web框架那也好辦,django就是一個(gè)python下的web框架,類似于php的thinkphp,java的struts等。
這第一個(gè)里程碑走到這里才走了三分之一,路漫漫其修遠(yuǎn)亦,想得到和辦得到差距還是挺大的,一個(gè)筋斗云十萬八千里,那是心的速度,實(shí)際的取經(jīng)路卻是八戒離開高老莊的那句話:此去千山萬水,路途遙遠(yuǎn),容我去與紅塵俗世道個(gè)別。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 今天對(duì)docker做一個(gè)梳理。關(guān)于docker的安裝和初步使用,上周已經(jīng)實(shí)踐過一次,今天就從安裝完成后開始逐步梳理...
    吳智深閱讀 376評(píng)論 0 1
  • 接著昨天的進(jìn)度,繼續(xù)梳理思路。運(yùn)維工作對(duì)我而言是陌生領(lǐng)域,涉及太多的配置工作,除了按手冊(cè)進(jìn)行配置,還要把每個(gè)配置的...
    吳智深閱讀 697評(píng)論 0 1
  • 參考 嗶哩嗶哩 官方電子書 環(huán)境 Centos 7.6 #未安裝Centos7,可以參考這篇文章 Docker 1...
    假裝我不帥閱讀 905評(píng)論 2 0
  • 今天摸索了一下docker的swarm集群管理技術(shù)。摸索的過程中走了兩次彎路。 第一次彎路: 玩集群首先得虛擬出幾...
    吳智深閱讀 813評(píng)論 0 1
  • 這兩天參加會(huì)務(wù)籌備耽誤了進(jìn)度,昨晚上花了3小時(shí)配置成功,趁熱打鐵把過程梳理一下。梳理思路時(shí)突然發(fā)現(xiàn)了一個(gè)悖論,一晚...
    吳智深閱讀 986評(píng)論 0 2

友情鏈接更多精彩內(nèi)容