Featured image of post Docker學習筆記

Docker學習筆記

Docker的一些學習筆記,想到啥就寫啥

名詞解釋

Container

Docker Container 是一種軟體容器,它可以在其中運行應用程式和其他服務。它使用操作系統級別的虛擬化,可以在單一的物理主機上運行多個容器,並且每個容器都有自己的運行環境和資源。

Docker Container 是一種軟體容器,它可以在其中運行應用程式和其他服務。容器具有輕量級、可移植性和隔離性等特點。容器是通過在操作系統內核中運行的容器引擎來實現的。

Docker容器主要是使用了Linux 的 Namespaces 和 Control groups(cgroups) 技術來實現隔離,這兩種技術可以將一個實體主機上的資源限制給每個容器,而容器內部則是共用一個kernel,因此容器比虛擬機器輕量且速度較快。

Docker容器是基於鏡像(Image)來建立與運行的,一個鏡像可以是一個基礎鏡像或是由其他鏡像所建立而來。當執行docker run 指令時,會從鏡像建立一個容器,並在容器內執行指定的應用程式或服務。

不僅如此,Docker容器還支援網路、儲存卷的映射,使得容器可以與外部通訊,也可以存取本地端的資料。

透過Docker容器,我們可以將應用程式、服務和其所需的環境打包在一起,並且可以在不同的環境中運行,提高了應用程式的可移植性和彈性。

Image

Docker Image 是 Docker 容器的基礎,它是一個只讀的模板,包含了容器運行所需的所有檔案、設定和程式。當執行 docker run 指令時,Docker 會從 Image 建立一個新的容器並在其中執行指定的應用程式或服務。

Docker Image 可以通過構建或下載的方式創建,構建的方式可以使用 Dockerfile 來描述如何構建一個 Image。而下載的方式則可以從 Docker Hub 或其他的 registry 下載。

Volume

Docker Volume 是 Docker 的一種功能,用於管理容器中的數據。容器本身是輕量級的,數據是不能永久存在的,而 Volume 則是可以永久存在的。

Docker Volume 可以被掛載到容器上,並且可以在容器內部存儲數據。當容器停止運行或者被刪除時,Volume 中的數據仍然可以保留下來。這樣就可以在重啟容器或建立新容器時,繼續使用之前存儲的數據。

並且Volume裡面的資料是可以和Host分享的,兩邊的資料呈現鏡像的雙向對應,在Host新增的東西會在Container出現,Container新增的資料也會在Host裡面出現

image-20230121163302976

常用指令

搜尋Image

1
docker search postgres

image-20230124042123305

查看目前的image

1
docker image ls

或是

1
docker images

執行docker image

1
docker run [imageName][:tag]

image-20230121123518172

後面的:latest是版本號,可加可不加,沒加的話預設就是latest

刪除images

1
docker rmi [imageId]

在Detached mode下執行

1
docker run -d nginx

所謂的Detached mode亦即啟動後會不會占用你的terminal,可以看一下下面的git,可以比較兩者間的差異

  • 沒有-d

demo

  • 有-d

demo

打包成docker image

1
docker build -t drink-more-water:latest .

-t 是 tag的縮寫,hello-docker是這個tag的名稱,.代表dockerfile在當前的目錄下,如果Dockerfile不在當前目錄,則這邊要改變。latest則是版本號,可加可不加,不加的話預設是latest

image-20230118215347469

查看目前運行的Container

1
docker ps

ps是process status的意思

或是

1
docker container ls

demo

查看目前運作中(running)的Container

1
docker ps

image-20230118222647084

或是

1
docker ps -a

查看底下全部的Container不論啟動與否

image-20230118222751647

進入Container與之互動

1
docker exec -it [ConatinerId]] bash

bash有可能沒有,有可能是sh,要自己到/bin裡面看

停止Container

1
docker stop [ContainerId]

image-20230121125714796

啟動停止的Container

1
docker start [ContainerId]

刪除Container

1
docker rm [ContainerId]or[NAMEs]

demo

也可以輸入很多個Id,一次刪個爽

image-20230121155539947

還有更猛的

1
docekr rm -f $(docker ps -aq)

直接用參數的方式全刪。

暴露port

1
docker run -p 5432:5432 posgres

前面的5432是你自定義的localhost:5432,而後面的5432則是容器裡面的port號

暴露已經Running,Stopping 的Container的Port

沒有這個方法,只有

1
docker run postgres

的這個時候你才可以把port暴露出來

一次性查看Container的Log紀錄

1
docker logs [ContainerId]

image-20230124052203569

這條指令只會顯示過去的紀錄,後續的logs不會更新

持續查看Container的Log紀錄

1
docker logs -f [ContainerId]

這條不只會顯示過去的,還會動態更新現在的log

執行Docker-compose

1
docker compose up -d

-d 代表是否背景執行,不佔用terminal

停止並刪除Docker-compose的Container

1
docker compose down

將Dokcer Images Push至Dockerhub

首先先登入dockerhub

1
docker login

再將想要推上去的docker Image重新命名

1
docker tag [Image Name] DockerHub帳號/Image Name

接著push上去 dockerhub

1
docker push DockerHub帳號/Image Name

想要使用image的話就執行pull

1
docker pull DockerHub帳號/Image Name

在Docker啟動Ubuntu

  1. 下載 ubuntu 的image

    1
    
    docker pull ubuntu
    

    或是可以

    1
    
    docker run ubuntu
    

    就會自動從docker hub載下來了,但這樣只是把ubuntu的image拉到我們的docker裡面,它本身是沒有啟動的

    image-20230118222929032

  2. 在docker中運行ubuntu

    1
    
    docker run -it ubuntu
    

  3. 使用apt(advanced package tool)安裝nano(Linux text editor)

    用apt載任何東西前都建議先update

    1
    
    apt update
    
    1
    
    apt install nano
    

Exposing Port

輸入docker ps可以看到以下資訊

image-20230121144512601

其中的PORTS 80/tcp的意思,容器對外公開的網路端口是 80/tcp,表示這個容器對外公開的網路端口是80,並且是基於TCP協議的。這意味著當外部網路瀏覽器連接到http://localhost或http://時,將會連接到容器內部的 Nginx Web 伺服器。想要讓容器的端口對外開放,就需要exposing它,否則直接打localhost:80是沒有用的。

demo

我們可以使用以下的方式將8080 連接到80/TCP

image-20230121144820173

1
docker run -d -p 8080:80 nginx

其中的8080:80的意思是指將主機的 8080 端口映射到容器的 80 端口。也就是說,當外部網路瀏覽器連接到 http://localhost:8080 時,將會連接到容器內部的 Nginx Web 伺服器。

demo

image-20230121152744371

你也可以不只Exposing一個Port,可以Exposing多個port給80

image-20230121153052811

1
docker  run -d -p 8080:80 -p 3000:80  nginx

image-20230121153324975

Container的管理

當我們啟動、並Stop一個Container,實際上如果依照我們剛剛的作法,我們是不斷的創造新的Container,輸入docker ps -a 即可看到目前存在的Container(不論running or Stopping),或是在Desktop docker裡面也都可以看到

demo

image-20230121154505315

我們可以透過

1
docker rm [ContainerId]

來真正意義上的移除Container,而不是停止它

demo

可以使用docker ps -aq ,這個指令只會秀出ContainerId,可以刪更爽,直接複製貼上就好

還有更爽的方式,用$(docker ps -aq)的方式 傳遞參數

demo

為Container命名

建議命名一下,比較好找,只支援英文,不支援中文

1
docker run --name hoxtonPractice -d -p 8080:80 nginx

demo

image-20230121161417305

Volume的使用

讓資訊可以在host與Container共享的一個功能

image-20230121171102777

範例:

首先在桌面上創建一個名為website的資料夾,裡面有個index.html,內容如下

1
<h1>hello docker and volume</h1>

image-20230121172021418

接著將terminal切至/website底下,然後輸入

1
docker run --name website -v ${PWD}:/usr/share/nginx/html:ro -d -p 8080:80 nginx
  • -v是 Docker 中的 volume 指令,它用於將主機上的目錄或檔案掛載到容器中。配合後面的${PWD}:/usr/share/nginx/html,意思就是將當前目錄的內容掛載(Mount)到容器中的 /usr/share/nginx/html 目錄下。這樣設定後,當主機上的目錄內容變更時,容器中的 /usr/share/nginx/html 目錄內的內容也會隨之更新。

  • :ro 是指將主機上的目錄或檔案掛載到容器中的目錄或檔案,並設定為只讀模式。

​ 這意味著在容器中將無法寫入或修改掛載的目錄或檔案,只能讀取。這可以避免對主機上的檔案造成損壞或不 必要的變更。

  • /usr/share/nginx/html 是 Nginx 預設的網站根目錄。

結果如下:

demo

也因為Volume是鏡像對應,因此修改host的檔案,container的內容物也會同步更新

demo

我們可以用以下的指令來訪問看看Nginx的檔案

1
docker exec -it website bash 
  • docker exec 是 Docker 的命令行工具,用於在運行中的容器內執行命令。
  • -it 這兩個選項表示要互動式地執行命令,並且讓輸入和輸出保持連接。
  • website 是容器的名稱或 ID。
  • bash 是要在容器內執行的命令,這裡是啟動 Bash shell。也可以改成ls,就變成ls了,玩法很多,自行摸索

demo

在Nginx裡面新增檔案,移除檔案,會發現host的資料夾檔案也同步更新

demo

不同的Container使用相同的Volume

image-20230122000944184

image-20230122000515311

1
docker run --name website-copy --volumes-from website -d -p 8081:80 nginx
  • –volumes-from [ContainerName]:將這次要啟動的Container使用和website一樣的Volume

image-20230122000709771

Dockerfile

Dockerfile是一個文本文件,它包含了創建Docker image所需的指令。這些指令可以包括例如:

  • 從哪個基礎鏡像建立新鏡像
  • 安裝需要的軟體
  • 設置環境變量
  • 添加應用程序文件
  • 定義容器啟動時執行的命令

透過Dockerfile, 可以自動化的建立一個環境,方便在不同的環境上部署,使用者可以更方便的管理環境,以及減少部署錯誤的機會。

例如,如果你有一個Java應用程序需要在多個不同的服務器上運行,你可以使用Dockerfile創建一個包含Java執行時環境的镜像,然後在每個服務器上執行這個镜像,這樣就能保證每個服務器上都有相同的環境。

試著把剛剛寫的volume打包成一個image,首先在/website的資料夾裡面新增一個名稱一定要是dockerfile的檔案

裡面的檔案結構長的像這樣子

1
2
FROM nginx:latest
ADD . /usr/share/nginx/html
  • FROM:指定了基礎Image是nginx,後面的latest是指版本號。Dockerfile中必須要有FROM指令,它是一切的根本,它指定了基礎Image環境。舉例來說,這邊指定用最新版的nginx,那麼我們的鏡像會基於這個最新版的nginx環境運行
  • ADD:將本地目錄中的文件複製到鏡像中的指定目錄。在這個例子中是將本地目錄中的所有文件複製到鏡像中的/usr/share/nginx/html目錄。它的功能跟COPY有點像,但是COPY用法比較單純,只能複製本地文件和目錄到鏡像中,而ADD指令還可以解壓縮tar文件並將其中的文件複製到鏡像中。

​ 值得注意的是

1
ADD .   /usr/share/nginx/html

這行指的是,將當前目錄的所有東西(以一個.表示)加入至Container中的/usr/share/nginx/html目錄中。在這個例子中就是將

image-20230122005758339

這些東西ADD進/usr/share/nginx/html裡面。

當Dockerfile寫好後,要開始bulid它,步驟如下

1
docker build -t website:latest .
  • 這個命令是在使用 Docker 建立一個新的鏡像檔,並標記為 “website:latest”。 “.” 表示當前目錄下的 Dockerfile 檔案將會被用來建立映像檔。這個命令將會建立一個名為 “website” 並且標記為 “latest” 的鏡像檔。

Build完後就會出現一個image了,輸入

1
docker images 

就可以查看目前擁有的images

image-20230122023400655

並且可以這個image可以運行我們剛剛對index的設定,輸入

1
docker run --name website -p 8080:80 -d website:latest

注意:這邊不需要再為website設置volume,因為我們已經將需要的東西打包進image裡面了。

輸入完後,在URL的地方輸入localhost:8080就可以看到我們剛剛設置的東西了。

gif如下

demo

實際演練 NodeJs

前置作業

安裝Node.js,這邊安裝為了快速,就直接用Choco來裝了,Choco的安裝如下

  1. 在Terminal中輸入,記得要以系統管理員身分輸入

    1
    
    Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
    
  2. 安裝完後安裝nodeJs

    1
    
    choco install nodejs
    
  3. 安裝完後隨便創一個資料夾,這邊命名叫做user-service-api

  4. 切換到該資料夾底下,並且npm init它

    1
    
    npm init
    
  5. 接著安裝express

    1
    
    npm install --save express
    
  6. 用好後檔案結構長這樣

    image-20230122032825912

  7. 在該目錄底下新增一個index.js的檔案,內容如下

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    const express = require('express')
    const app = express()
    const port = 3000
       
    app.get('/', (req, res) => {
      res.json([{
        name: 'Bod',
        email: 'bob@gmail.com'
      }])
    })
       
    app.listen(port, () => {
      console.log(`Example app listening on port ${port}`)
    })
       
       
    
  8. 使用nodejs運行

    1
    
    node index.js
    
  9. 成功後進入localhost:3000即可看到下列畫面

    image-20230122033843917

如此一來前置作業就完成了

製作DockerFile

在目錄底下新增dockerfile,內容如下

1
2
3
4
5
FROM node:latest
WORKDIR /app
ADD . .
RUN npm install
CMD node index.js
  • WORKDIR:若Container有/app這個資料夾,則使用它,若沒有,則創造它。
  • ADD . .: 将当前目录中的文件复制到镜像中的 /app 目录。
  • RUN npm install: 在鏡像中运行 npm install 命令,安装应用程序所需的依赖项。
  • CMD node index.js:设置镜像启动时运行的命令,这里是运行 node index.js。

為什麼要分成RUN跟CMD呢?有幾個原因,首先RUN跟CMD的用途本身就不一樣,RUN主要是在創建image中執行命令,並將結果保存在image中,它主要用來安裝依賴、配置應用程式或其他操作。則是用來說明Image創建完成後要執行的動作。簡而言之,RUN是在創建Image中過程所執行的,而CMD則是在Image創建完成後所執行的

並且,一個DockerFile可以有很多RUN指令,但只能有一個CMD指令,因為Container只能運行一個CMD指令

使用Image

接著創建鏡像

1
docker build --tag user-service-api:latest .

創建完之後啟動鏡像

1
docker run --name user-api -d -p 8080:3000 user-service-api:latest

這邊的8080:3000是指,將我們容器裡面原本配置的3000端口暴露出來,以8080來接收。

image-20230122041621171

因為3000是指在Container裡面的端口,host想要讀到它,必須將Container的端口暴露出來。因此localhost:3000會找不到東西,只有打localhost:8080才會有我們要的內容

demo

DockerIgnore

做完上面這些操作後,我們的檔案結構長這樣

image-20230122042649897

然後我們的Dockerfile長這樣

1
2
3
4
5
FROM node:latest
WORKDIR /app
ADD . .
RUN npm install
CMD node index.js

比較之後發現一件事情,RUN npm install會創建node_modules資料夾,但我們在ADD時已經把node_modules加入進去,等於說我們重複創建了兩次node_modules,這種情況就類似gitIgnore,需要排除掉重複的資料夾

dockerIgnore的寫法

1
2
3
node_modules
dockerfile
.git

image-20230122043320515

這樣就可以把這些檔案排除在外了

Caching & Layers

DockerFile裡面的每一個CML都是一個Layer,每個Layer都用來Caching`

1
2
3
4
5
FROM node:latest
WORKDIR /app
ADD . .
RUN npm install
CMD node index.js

image-20230124033035860

可以看到這邊的Step1, Step2都對應著CML的指令…

而Cache的點就在於,其實除了ADD . . 以外(原始碼每次打包時都會有更動),其實WORKDIR, RUN npm install這些指令其實都是重複的,我們每次打包都需要再重複執行一次,這樣很沒**效率 **

於是Dokcer就會把這些重複的事情Caching起來,只要沒有改變就不會重複再做,就會看到上面的Using Cache了

ALPINE

翻譯的意思是高山

image-20230124040633982

image-20230124040527548

我剛剛打包的Image檔案已經快逼近一個G了,很明顯我們其實不需要那麼多的東西,Alpine版本的就是一個非常小的鏡像。

實際安裝ALPINE

1
docker pull node:lts-alpine

image-20230124041533774

image-20230124041614756

兩者的Size差了快十倍

Docker Compose 將後端與資料庫一起包一包

一個Project不可能只由一個後端組成,肯定是要由後端、前端、以及資料庫三者組合,甚至更甚者可能會有10,20個的部件需要去組合,那麼一個一個run container這件事情就變得相當缺乏效率。為了處理這件事情,於是有了Docker-Compose的概念出現。

Docker-Compose大概就像這樣,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: '3.7'
services:
  db:
    container_name: postgres-for-dockerpractice
    image: postgres
    environment:
      POSTGRES_PASSWORD: root
      POSTGRES_USER: root
      POSTGRES_DB: root
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    ports:
      - '5432:5432'


  backend:
    container_name: backend-for-dockerpractice
    image: shop
    depends_on:
      - db
    ports:
      - '8080:8080'

和dockerfile一樣存在於專案根目錄中

image-20230130235149649

他類似於一個配置檔,用以告訴Docker要啟動哪些Container,以及它們之間的交互關係,以上面的Docker-compose.yml來說明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
version: '3.7' //要使用的dokcer-compose版本,有分很多版,比如說1.0,2.0,但目前主流是3.0,所以照著寫就好
services: //每一個起起來的Container有一個特殊的名字,叫做service,這個yaml檔就有兩個service,分別叫db跟backend
  db: //可自定義的service名稱,高興叫啥就叫啥,但這個名字會與你在application.properties裡描述的名稱有對應關係,可以看看下面的附圖,第四行的url:jdbc:postgresql://db:5432/shop,其中的db就是service的名稱
    container_name: postgres-for-dockerpractice //自定義的名稱,想叫啥就叫啥,這名稱會是你的Container名稱
    image: postgres //要使用哪個image作為基底
    environment:
      POSTGRES_PASSWORD: 45002502
      POSTGRES_USER: postgres
      POSTGRES_DB: shop
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    ports:
      - '5432:5432' //要暴露出來的端口
  backend: //可自定義的service名稱,高興叫啥就叫啥,但這個名字會與你在application.properties裡描述的名稱有對應關係。
    container_name: backend-for-dockerpractice  //自定義的名稱,想叫啥就叫啥,這名稱會是你的Container名稱
    image: shop //要使用哪個image作為基底,這個是我自己docker build -t shop:latest . 所創建出來的image
    depends_on:
      - db //這意味著,你這邊的service會等到db這個service完成後才會進行部屬。
    ports:
      - '8080:8080' // 暴露出來的端口

image-20230130235821812

▲第四行的db與services的名稱有對應。

image-20230131000230558

▲上述的docker-compose啟動後顯示的樣子。

常用的指令

啟動當前目錄的docker-compose

1
docker compose up

關閉當前目錄的docker-compose

1
docker compose down
Licensed under CC BY-NC-SA 4.0