看完此文,媽媽還會擔心你docker入不了門?

本文在個人技術博客不同步發布,詳情可猛戳
亦可掃描屏幕右側二維碼關注個人公眾號,公眾號內有個人聯系方式,等你來撩...

??上周對象突然心血來潮說想養個小寵物,我問想養啥她又說隨便,你看著辦!!!這我真的比較難辦啊!但是咱們程序員能有個對象就不錯了,還不趕緊寵著,我只能照辦咯!

??我去到了一家寵物店,半天也沒有找到合適的目標。正在我猶豫彷徨之時,看到了老板門口魚缸里面的金魚游來游去還挺順眼!于是我問老板
??我:老板,金魚多少錢?
??老板:加魚缸一起100塊錢不講價!
??我:這...便宜一點咯!
??老板:小伙子看你骨骼驚奇,定是個養魚的奇才,2塊錢賣給你吧!但是魚缸可不能給你!
??我:那,你幫我打包一條吧,幫我拿個袋子裝著就好了!

??于是我興高采烈的拎著小金魚就回家了,找了個大罐子養著!對象看到我買的小金魚后露出了幸福的笑容~
??第二天早上對象把我從睡夢中搖醒:“嚶嚶嚶,人家的小魚動不了了,你賠~~~ ”。于是我很憤怒的跑去寵物店找老板索賠!
??我:你們家賣的魚有問題,回去就不行了!
??老板:不可能,昨天在我們這都活蹦亂跳的!
??我:就是你們家的魚有問題!
??老板:肯定是你自己買的魚缸有問題!
??我:手持兩把錕斤拷,口中疾呼燙燙燙。
??老板:腳踏千朵屯屯屯,笑看萬物锘锘锘?

??這一幕,似曾相識!像極了我們在開發中
??測試:xx,你的代碼在生產環境上運行有問題。
??我:不可能,我本地都運行得好好的。
??測試:你自己上生產環境上看。
??我:我不看,我的代碼在本地沒問題,肯定是運維的鍋,你去找運維!

??或許很多開發人員都有過上面的經歷,程序在本地運行都很正常,一上到生產環境就崩了。這是因為程序跟小金魚一樣也會“水土不服”!而導致程序水土不服的原因一般就是環境配置的差異!加上現在互聯網高并發、大流量的訪問,一個應用往往需要部署到集群的多臺機器上,并且集群擴容縮容的需求也比較頻繁。如果按照傳統的方式部署,那每一臺服務器上都需要裝各種軟件...然后進行各種配置...我仿佛看到了“工作996,生病ICU”在向運維工程師招手!

??那有沒有一種方案不僅能屏蔽環境的差異,并且還能快速部署呢?既然“水土不服”那我把程序及整個“水土”都打包遷移,就看你服不服。而docker就是這樣的一種讓你服技術!

??昂,上面的對象是我自己new的(* ̄︶ ̄)...

Hello Docker

??Docker是什么呢?百度百科是這樣跟我說的:Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的鏡像中,然后發布到任何流行的 Linux或Windows 機器上,也可以實現虛擬化。容器是完全使用沙箱機制,相互之間不會有任何接口。

??這段話已經很概括的描述了docker是什么,docker能干嘛,docker的基本特性!相信剛開始接觸docker的你跟我也一樣,看了幾遍都很懵逼!沒關系,看完全文再回頭看這段話,或許就有不一樣的體會了!我們先看看docker官方給出的“定妝照”

??如果非要我用一句話描述這張圖片,還在上幼兒園的我會說:“一條可愛的鯨魚背著多個集裝箱,暢游在大海里 ”!而現在我會說:“docker是一個運行在操作系統上的軟件,這個軟件上面可以運行多個相互隔離的容器”!不同的表述,同一個意思!這條可愛的鯨魚就是咱們的docker,而大海就是我們的操作系統,多個集裝箱就是在docker上運行的容器!什么是容器咱們后面會說~

??假如你想漂洋過海來看我,你可以選擇自己造一條船,這樣你就得自己備足很多干糧,還得準備很多一些其他的必需品才能出發!但是現在有一條鯨魚游過來對你說,我這里有很多集裝箱,里面有你所需要的一切,你選一個適合你的進來就可以了,我會帶你乘風破浪的!

??看到這里,你是否對docker有個初步的印象了呢?至少知道了:1、docker是什么?2、為什么需要docker?

與傳統虛擬機對比

??前面我們說過docker可以實現虛擬化,那docker與我們平時用的虛擬機有什么區別和聯系呢?在那些年我們還買不起云服務器的時候,如果我們想學linux那就得先安裝一個創建虛擬機的軟件,然后在軟件上面創建虛擬機,然后分配內存、分配磁盤、安裝linux操作系統等等一系列的操作,然后等個分把鐘讓虛擬機運行起來~
??為什么傳統虛擬機啟動會那么慢呢?因為傳統虛擬機技術是虛擬出一套硬件后,在其上面運行一個完整的操作系統,然后在該系統上面再運行所需要的應用程序,并且虛擬機的資源需要提前分配,一旦分配這些資源將全部被占用。但是docker容器內的應用程序是直接運行于宿主的內核,容器沒有自己的內核,更加不會對硬件進行虛擬。因此docker容器比傳統的虛擬機更為輕便!但是docker容器技術也是參考虛擬機一步一步的迭代優化過來的!我們來看看官方給出的docker容器和傳統虛擬機的對比圖:

??圖中也能看出來,docker就是一個運行在操作系統上的軟件!以后如果想在windows上面學習Linux,只需要在本地安裝一個windows版本的docker,然后看完本文的剩下的部分,就能輕輕松松的玩轉linux啦!不過在windows上安裝docker也需要先安裝一個虛擬機~

基本組成要素

??前面對docker的基本概念有了個大致印象,但是到目前為止,可能你對docker的認識還比較空泛,那下面部分我們就從docker的基本組成要素來更深入的走進docker!docker是一個client-server的結構!先看看官網給出的架構圖:

??這張圖里面概括了docker的所有的元素!我們就逐一分析docker客戶端、docker服務、倉庫、鏡像、容器等概念!

docker客戶端

??最左邊是docker的客戶端,類似我們操作mysql的工具navcat,只不過我們這里的是沒有圖形化界面的命令終端。docker客戶端是用戶與docker服務交互的窗口!我們能看到圖中就是各種操作的命令!

docker服務

??中間的是docker后臺運行的服務,一個稱為docker daemon的守護進程。可以理解為我們mysql的服務,我們的操作命令都是在這部分進行處理!docker deamon監聽著客戶端的請求,并且管理著docker的鏡像、容器、網絡、磁盤(圖中只列出了鏡像與容器)等對象。同樣,docker的客戶端與服務可以運行在同一機器上,也可以用某臺機器上的客戶端遠程連接另一臺機器上的docker服務,這跟我們的mysql一樣的呢。

倉庫

??右邊部分是注冊倉庫,在遠古時代做開發的都知道,我們以前需要一個第三方包的時候需要去網上下載對應的jar包,很麻煩不說,還容易下的包是不穩定的版本。有了maven之后,我們只要在maven配置文件中引入對應的依賴,就可以直接從遠程倉庫中下載對應版本的jar包了。docker中的倉庫與maven的倉庫是一個概念,可以遠程下載常用的鏡像,也可以push包到遠程倉庫(如圖中的redis、nginx等鏡像),同一個鏡像又可以有多個版本,在docker中稱為tag!

鏡像&容器

??前面我們有多次提到鏡像和容器,這是docker里面很核心的兩個概念。那鏡像和容器分別是什么呢?鏡像和容器的關系是什么呢?

??鏡像
??官方給出的定義是:docker鏡像是一個只讀模板,可以用來創建docker容器。鏡像是一種輕量級的、可執行的獨立軟件包,用來打包軟件運行環境和基于運行環境開發的軟件。它包含運行某個軟件所需要的所有的內容,包括代碼、運行時、庫、環境變量、配置文件等。我們開發的web應用需要jdk環境、需要tomcat容器、需要linux操作系統,那我們可以把我們所需要的一切都進行打包成一個整體(包括自己開發的web應用+jdk+tomcat+centos/ubuntu+各種配置文件)。打包后的鏡像在某臺機器上能運行,那它就能夠在任何裝有docker的機器上運行。

??任何鏡像的創建會基于其他的父鏡像,也就是說鏡像是一層套一層,比如一個tomcat鏡像,需要運行在centos/ubuntu上,那我們的tomcat鏡像就會基于centos/ubuntu鏡像創建(在后面的操作部分我們可以通過命令查看),這樣的結構就類似于我們吃的洋蔥,如果你愿意一層一層一層地剝開我的心~

??容器
??官方給出的定義是:docker的容器是用鏡像創建的運行實例,docker可以利用容器獨立運行一個或一組應用。我們可以使用客戶端或者API控制容器的啟動、開始、停止、刪除。每個容器之間是相互隔離的。上一步我們構建的鏡像只是一個靜態的文件,這個文件需要運行就需要變為容器,我們可以把容器看做是一個簡易版的linux系統和運行在其中的應用程序!就是前面看到的鯨魚背上的一個一個的集裝箱,每個集裝箱都是獨立的!

??鏡像與容器關系
??上面的概念很抽象,可以理解為容器就是鏡像的一個實例,相信大家都寫過類似下面的代碼:

public void Dog extends Animal{  
    ......
}  
......
Dog dog = new Dog()

??我們在代碼中定義了一個Dog類,這個類就相當于一個鏡像,可以根據這個類new出很多的實例,new出來的實例就相當于一個個的容器。鏡像是靜態的文件,而容器就是有生命的個體!Dog類可以繼承父類Animal,如果不顯式的指定繼承關系,Dog類就默認繼承Object類。同樣上面也說到過docker中的鏡像也有繼承關系,一個鏡像可以繼承其他的鏡像創建,添加新的功能!

??看到這里的你是不是對docker有了更多的了解了呢?我們再回頭看看百度百科對docker的描述,可能你又會有更深的印象:
??Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的鏡像中,然后發布到任何流行的 Linux或Windows 機器上,也可以實現虛擬化。容器是完全使用沙箱機制,相互之間不會有任何接口。

容器數據卷

??上面說到容器是一個簡易版的linux系統和運行在其中的應用程序,那我們的應用程序產生的數據(比如操作日志、異常日志、數據)也是在容器內的系統中存放的,默認不會做持久化,我們可以進入到容器中查看。但是萬一有一天,docker這條鯨魚不滿人類的壓迫,反抗了...老子打爛你的集裝箱!

??隨著容器的關閉,容器內的數據也會丟失,重新開啟的容器不會加載原來的數據(簡單說就是容器重新啟動又是另外一個實例了)。那對容器內的數據需要持久化到宿主機上就很有必要了,這就需要了解我們的容器數據卷~

??容器數據卷的設計目的就是做數據的持久化容器間的數據共享,數據卷完全獨立于容器的生命周期,也就是說就算容器關閉或者刪除,數據也不會丟失。簡單點說就將宿主機的目錄掛在到容器,應用在容器內的數據可以同步到宿主機磁盤上,這樣容器內產生的數據就可以持久化了。關于容器卷的命令我們后面會有操作實例!

命令操作

??上面說了那么多,下面就到了咱們的實操環節啦!這一節的內容會通過一些常用的命令讓大家更進一步的了解docker,注意!!這里只是一些常用的命令來加深理解,而不是命令大全!如果沒有安裝docker的小伙伴可以自己按照官網的文檔進行安裝,本文不會講到這部分的內容!所以我假設你在自己的服務器上已經裝好了docker!

幫助命令

??1、docker version 查看docker客戶端和服務的版本。

??2、docker info 查看docker的基本信息,如有多少容器、多少鏡像、docker根目錄等等。

??3、docker --help 查看docker的幫助信息,這個命令可以查看所有docker支持的命令~

??這幾個命令非常簡單,有過一點linux基礎的小伙伴應該很容易理解!

鏡像命令

??1、docker images 查看本地主機上所有的鏡像。注意是本地主機的!這里能看到鏡像的名稱、版本、id、大小等基本信息,注意這里的image ID是鏡像的唯一標識!還可以通過docker images tomcat指定某個具體的鏡像查看對應信息。這里還要注意的是centos的鏡像才200MB的大小,比我們物理機器上裝的centos要小得多的多,這是因為centos的鏡像只保留了linux核心部分,這也是為什么docker虛擬化技術比虛擬機運行效率更高的原因!那為什么tomcat的鏡像這么大呢?那是因為我們之前說過我們的鏡像就像一個洋蔥一樣,是一層套一層的!tomcat的運行需要基于centos、jdk等等鏡像,tomcat在上層所以體積比較大啦!
??

??2、docker rmi 刪除本地的鏡像,如下圖所示,可以加上-f參數進行強制刪除。這里的rmi命令跟linux中的刪除命令就很像啦,只是這里加了一個i代表image!
??

??3、docker search 根據鏡像名稱搜索遠程倉庫中的鏡像!
??

??4、docker pull 搜索到某個鏡像之后就可以從遠程拉取鏡像啦,有點類似咱們git中的pull命令,當然對應的還有個docker push的命令。如圖,如果我們沒有指定tag,默認就會拉取latest版本,也可以通過docker pull tomcat:1.7的方式拉取指定版本!注意這里在拉取鏡像的時候打印出來的信息有很多,這也是前面說到的鏡像是一層套一層,拉取一個鏡像也是一層一層的拉取!
??

容器命令

??通過鏡像命令我們就能獲取鏡像、刪除鏡像等操作啦!鏡像有了下面自然就需要通過鏡像創建對應的實例啦,也就是我們的容器。下面我們以tomcat為例:

??1、docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 可以基于某個鏡像運行一個容器,如果本地有指定的鏡像則使用本地鏡像,如果沒有則從遠程拉取對應的鏡像然后啟動!由于這個命令非常重要,所以下面列出幾個比較重要的參數:

-d:啟動容器,并且后臺運行(docker容器后臺運行,就必須要有一個前臺進程,容器運行的命令如果不是一直掛起的命令,容器啟動后就會自動退出);
-i:以交互模式運行容器,通常與-t同時使用;
-t:為容器重新分配一個偽輸入終端,通常與-i同時使用(容器啟動后進入到容器內部的命令窗口);
-P:隨機端口映射,容器內部端口隨機映射到主機的高端口;
-p:指定端口映射,格式為:主機(宿主)端口:容器端口;
-v:建立宿主機與容器目錄的同步;
--name="myTomcat": 為容器指定一個名稱(如果不指定,則有個隨機的名字);

??

??上面我通過命令啟動了一個tomcat的容器,由于使用了 -t 的參數,所以容器啟動后就進入到了容器的內部的命令窗口,打印了很多tomcat啟動的日志。并且使用 -p 參數指定了端口映射,也就是容器內tomcat運行的端口是8080,并且映射到了宿主機上的8888端口,這樣我們在外部就可以同過服務器的ip+8888端口 訪問到我們容器內部tomcat部署的服務了。

??前面我們提到過容器內的數據會隨著容器的關閉而丟失。那我們就需要有容器數據卷的技術能將容器內的數據持久化到宿主機。這里需要用到 -v 參數!我們看下面的截圖
??

??這里第一個要注意的是我們用的 -d 參數,啟動后沒有進入到容器內部,還是在宿主機。(可以對比一下與上面 -it 參數的區別)。第二個要注意的是 -v /宿主機:/容器內目錄 實現了宿主機與容器內指定目錄的數據同步!容器啟動后就可以使用 linux 的 ll 命令查看宿主機上已經同步到了容器內的文件。第三個要注意的是這里的同步是雙向的,也就是說在宿主機上對文件的修改也會同步到容器內部!多個不同的容器映射到宿主機的同一個目錄,就可以實現不同容器間的數據共享啦!
??

??2、進入到容器后可以通過exit命令退出容器,也可以通過ctrl+P+Q快捷鍵退出容器,這兩種方式的不同之處是exit會退出并且關閉容器,而ctrl+P+Q快捷鍵只是單純的退出,容器還在運行,并且還能再次進入!

??3、docker ps我們可以通過該命令查看正在運行的容器的信息,這里能看到容器的唯一id,啟動時間等等...這里跟linux的ps命令類似,所以也可以把容器理解為一個運行在docker上的進程!docker ps -a可以查看運行中與停止的所有容器。
??

??4、docker attach [OPTIONS] CONTAINER上面說過通過ctrl+P+Q快捷鍵退出容器后容器還在后臺運行,那如果想再次進入容器怎么辦呢?我們就可以通過attach命令+容器的id再次進入容器!

??5、docker exec [OPTIONS] CONTAINER這個命令與attach一樣都可以再次進入后臺運行的容器,但是該命令可以不進入容器而在運行的容器中執行命令!比attach更加強大!

??6、docker stop docker kill docker restart這三個命令分別用來停止容器、強制停止容器和重啟容器,就跟我們在linux上停止、強制停止和重啟某個進程一樣的啦,這里就不做演示了!

??7、docker rm 使用這個命令就可以刪除某個容器,這里跟刪除鏡像的區別是這里少了一個 i 啦!需要注意的是通過stop和kill停止的容器還存在于docker中,而使用 rm 命令操作后的容器將不再存在!
??

??8、docker inspect 查看容器的詳情(也能查看鏡像詳情)。

Dockerfile

??前面我們對docker以及相關概念、常用命令有了基本的了解,我們也知道了可以從遠程pull一個鏡像,那遠程的鏡像是怎么來的呢?如果我們想自己創建一個鏡像又該怎么做呢?
對,Dockerfile!Dockerfile是一個包含用戶能夠構建鏡像的所有命令的文本文檔,它有自己的語法以及命令,docker能夠從dockerfile中讀取指令自動的構建鏡像!
??我們要想編寫自己的Dockerfiler并構建鏡像,那對Dockerfile的語法和命令的了解就是必須的,了解規則才好辦事嘛!

相關指令

FROM
??FROM <image> [AS <name>]
??FROM <image>[:<tag>] [AS <name>]
??FROM <image>[@<digest>] [AS <name>]
??指定基礎鏡像,當前鏡像是基于哪個鏡像創建的,有點類似java中的類繼承。FROM指令必是Dockerfile文件中的首條命令。

MAINTAINER
??MAINTAINER <name>
??鏡像維護者的信息,該命令已經被標記為不推薦使用了。

LABEL
??LABEL <key>=<value> <key>=<value> <key>=<value> ...
??給鏡像添加元數據,可以用LABEL命令替換MAINTAINER命令。指定一些作者、郵箱等信息。

ENV
??ENV <key> <value>
??ENV <key>=<value> ...
??設置環境變量,設置的變量可供后面指令使用。跟java中定義變量差不多的意思!

WORKDIR
??WORKDIR /path/to/workdir
??設置工作目錄,在該指令后的RUN、CMD、ENTRYPOINT, COPY、ADD指令都會在該目錄執行。如果該目錄不存在,則會創建!

RUN
??RUN <command>
??RUN ["executable", "param1", "param2"]
??RUN會在當前鏡像的最上面創建一個新層,并且能執行任何的命令,然后對執行的結果進行提交。提交后的結果鏡像在dockerfile的后續步驟中可以使用。

ADD
??ADD [--chown=<user>:<group>] <src>... <dest>
??ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
??從宿主機拷貝文件或者文件夾到鏡像,也可以復制一個網絡文件!如果拷貝的文件是一個壓縮包,會自動解壓縮!

COPY
??COPY [--chown=<user>:<group>] <src>... <dest>
??COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
??從宿主機拷貝文件或者文件夾到鏡像,不能復制網絡文件也不會自動解壓縮!

VOLUME
??VOLUME ["/data"]
??VOLUME用于創建掛載點,一般配合run命令的-v參數使用。

EXPOSE
??EXPOSE <port> [<port>/<protocol>...]
??指定容器運行時對外暴露的端口,但是該指定實際上不會發布該端口,它的功能是鏡像構建者和容器運行者之間的記錄文件。
??回到容器命令中的run命令部分,run命令有-p和-P兩個參數,如果是-P就是隨機端口映射,容器內會隨機映射到EXPOSE指定的端口,如果是-p就是指定端口映射,告訴運維人員容器內需要映射的端口號。

CMD
??CMD ["executable","param1","param2"]
??CMD ["param1","param2"]
??CMD command param1 param2
??指定容器啟動時默認運行的命令,在一個Dockerfile文件中,如果有多個CMD命令,只有一個最后一個會生效!同樣是可以執行命令,可能你會覺得跟上面的RUN指令很相似,RUN指令是在構建鏡像時候執行的,而CMD指令是在每次容器運行的時候執行的!docker run命令會覆蓋CMD的命令!

ENTRYPOINT
??ENTRYPOINT ["executable", "param1", "param2"]
??ENTRYPOINT command param1 param2
??這個指令與CMD指令類似,都是指定啟動容器時要運行的命令,如果指定了ENTRYPOINT,則CMD指定的命令不會執行!在一個Dockerfile文件中,如果有多個ENTRYPOINT命令,也只有一個最后一個會生效!不同的是通過docker run command命令會覆蓋CMD的命令!執行的命令不會覆蓋ENTRYPOINT,docker run命令中指定的任何參數都會被當做參數傳遞給ENTRYPOINT!

RUN、CMD、ENTRYPOINT區別
1、RUN指令是在鏡像構建時運行,而后兩個是在容器啟動時執行!
2、CMD指令設置的命令是容器啟動時默認運行的命令,如果docker run沒有指定任何的命令,并且Dockerfile中沒有指定ENTRYPOINT,那容器啟動的時候就會執行CMD指定的命令!有點類似代碼中的缺省參數!
3、如果設置了ENTRYPOINT指令,則優先使用!并且可以通過docker run給該指令設置的命令傳參!
4、CMD有點類似代碼中的缺省參數

USER
??USER <user>[:<group>]
??USER <UID>[:<GID>]
??用于指定運行鏡像所使用的用戶。

ARG
??ARG <name>[=<default value>]
??指定在鏡像構建時可傳遞的變量,定義的變量可以通過docker build --build-arg =的方式在構建時設置。

ONBUILD
??ONBUILD [INSTRUCTION]
??當所構建的鏡像被當做其他鏡像的基礎鏡像時,ONBUILD指定的命令會被觸發!

STOPSIGNAL
??STOPSIGNAL signal
??設置當容器停止時所要發送的系統調用信號!

HEALTHCHECK
??HEALTHCHECK [OPTIONS] CMD command (在容器內運行運行命令檢測容器的運行情況)
??HEALTHCHECK NONE (禁止從父鏡像繼承檢查)
??該指令可以告訴Docker怎么去檢測一個容器的運行狀況!

SHELL
??SHELL ["executable", "parameters"]
??用于設置執行命令所使用的默認的shell類型!該指令在windows操作系統下比較有用,因為windows下通常會有cmd和powershell兩種shell,甚至還有sh。

構建

??Dockerfile執行順序是從上到下,順序執行!每條指令都會創建一個新的鏡像層,并對鏡像進行提交。編寫好Dockerfile文件后,就需要使用docker build命令對鏡像進行構建了。
??docker build的格式:docker build [OPTIONS] PATH | URL | -

-f :指定要使用的Dockerfile路徑,如果不指定,則在當前工作目錄尋找Dockerfile文件!
-t: 鏡像的名字及標簽,通常 name:tag 或者 name 格式;可以在一次構建中為一個鏡像設置多個標簽。

??例如我們可以docker build -t myApp:1.0.1 .這樣來構建自己的鏡像,注意后面的 . ,用于指定鏡像構建過程中的上下文環境的目錄。如果大家想了解那些官方鏡像的Dockerfile文件都是怎么樣寫的,可以上https://hub.docker.com/ 進行搜索,以tomcat鏡像為例

??能看到tomcat鏡像的父鏡像是openjdk鏡像,我們再搜索openjdk的Dockerfile文件

??openjdk鏡像的父鏡像又是oraclelinux鏡像,我們再搜索oraclelinux的Dockerfile文件


??openjdk鏡像的父鏡像是scratch,這是根鏡像,所有的鏡像都會依賴該鏡像,就像我們代碼中所有的對象的父類都是Object!所以能看到tomcat鏡像就是這樣一層一層的構建出來的,這也是為什么前面通過docker images查看到的tomcat鏡像為什么會有四百多兆的原因啦!

??看到這里的你,是否對docker是什么?為什么需要docker?docker鏡像、docker容器的概念是什么?docker中常用的命令有哪些?Dockerfile有哪些指令?怎么去構建自己的鏡像?這些問題都能明白了呢?

posted @ 2019-06-17 09:50 蘇蘇喂 閱讀(...) 評論(...) 編輯 收藏
四川金7乐历史开奖号码查询