# docker ###### tags: `devops` ## docker version ```docker Client: Cloud integration: v1.0.24 Version: 20.10.17 API version: 1.41 Go version: go1.17.11 Git commit: 100c701 Built: Mon Jun 6 23:04:45 2022 OS/Arch: darwin/amd64 Context: default Experimental: true Server: Docker Desktop 4.10.1 (82475) Engine: Version: 20.10.17 API version: 1.41 (minimum version 1.12) Go version: go1.17.11 Git commit: a89b842 Built: Mon Jun 6 23:01:23 2022 OS/Arch: linux/amd64 Experimental: true containerd: Version: 1.6.6 GitCommit: 10c12954828e7c7c9b6e0ea9b0c02b01407d3ae1 runc: Version: 1.1.2 GitCommit: v1.1.2-0-ga916309 docker-init: Version: 0.19.0 GitCommit: de40ad0 ``` **`docker system df`** 查看docker磁碟運行狀況 **`docker images -qa`** 列出所有image id **`docker rmi -f $(docker images -qa)`** 刪除所有image,docker可以帶入變數去執行結果 **`docker rmi $(docker images --filter "dangling=true" -q)`** 刪除所有未被標記的image ## CONTAINER **`docker ps -a`** 查看當前的container **`docker ps -l`** 查看上一個創建的container **`docker ps -n 4`** 查看前四個創建的container **`docker -q`** 查看containerID **`docker run`** 開啟新的container並啟用image **`docker run --rm hello-world`** 開啟新的container並啟用image,執行結束自動刪除 **`docker start [containerID]`** 開啟container **`docker stop [containerID]`** 停止使用container **`docker restart [containerID]`** 重啟container **`docker kill [containerID]`** 強制停止container **`docker rm [containerID]`** 刪除container **`docker rm [containerID] -f`** 強制刪除container **`docker rm $(docker ps -a -q) -f`** 一次刪除多個container **`docker images -q| tail -5`** 列出前5筆新創建的image **`docker images prune`** 移除所有虛懸鏡像 **` docker run --env-file ./.env -dp 3000:3000 6abfa65e229f `** 在image 中設定環境變數,掛載local的.env **`docker rm $(docker ps -a -q) -f`** 一次刪除多個container ## VOLUME docker 儲存持久化數據的地方,概念像是虛擬化資料夾,有時候我們docker在run時沒有用-v會發生一件事,當container一但消失container中所有的設定黨或是data都會全部不見,這時候可以夠過-v的方式指定檔案放到volume資料夾中,這樣以後如果container移除,下一次要新增新的container時就可以參照之前的voiume資料夾就不用重新設定。 ### commands **`create`** 新增一個 volume **`inspect`** 檢視volumn資訊 ### volumes **`ls`** List volumes **`prune`** Remove all unused local volumes **`rm`** Remove one or more volumes **繼承其他container 的volumn** docker run --volumes-from [containerID] ## Dockerfile指令說明: ```docker= FROM 構建鏡像基於哪個鏡像 MAINTAINER 鏡像維護者姓名或郵箱地址 RUN 構建鏡像時運行的指令 CMD 運行容器時執行的shell環境 VOLUME 指定容器掛載點到宿主機自動生成的目錄或其他容器 USER 為RUN、CMD、和ENTRYPOINT 執行命令指定運行用戶 WORKDIR 為RUN、CMD、ENTRYPOINT、COPY 和ADD 設置工作目錄,就是切換目錄 HEALTHCHECH 健康檢查 ARG 構建時指定的一些參數給FROM參照 EXPOSE 聲明容器的服務端口(僅僅是聲明) ENV 設置容器環境變量 ADD 拷貝文件或目錄到容器中,如果是URL或壓縮包便會自動下載或自動解壓 COPY 拷貝文件或目錄到容器中,跟ADD類似,但不具備自動下載或解壓的功能 ENTRYPOINT 運行容器時執行的shell命令 ``` ## ENV 設置dockerfile變數使用,可以讓後續指定的指令吃到變數的值 ```dicker= From resin/rpi-raspbian ENV NODE_VER=node-v5.9.1-linux-armv7l ADD ./${NODE_VER} / RUN ln -s /${NODE_VER} /node CMD ["node"] ``` 定義第二行變數讓ADD可以添加,如果ENV沒有指定,第三行的ADD就變成空值。 **` docker run --env-file ./.env -dp 3000:3000 6abfa65e229f `** 在image 中設定環境變數,掛載local的.env ## ARG 設置docker-build變數使用 ```dicker= From resin/rpi-raspbian ARG NODE_VER ADD ./${NODE_VER:-node-v5.9.1-linux-armv7l} / RUN ln -s /${NODE_VER} /node ENV PATH=/node/bin:$PATH CMD ["node"] ``` 在build的時候就要讓指定--build-arg讓Dockerfile得ARG可以吃到。 `docker build --build-arg NODE_VER=node-v5.9.0-linux-armv7l .` `ADD ./${NODE_VER:-node-v5.9.1-linux-armv7l} /` : 當build沒有指定ARG的NODE_VER時就會預設-node-v5.9.1-linux-armv7l檔案 ### ENV VS ARG 1. arg可以達到build時的自由化,透過--build-arg 改變輸出結果。 2. env可以減少dockerfile的複寫,也便於修改參數結果。 | | flag | run |build | -------- | -------- | -------- |----- | ARG | --build-arg | no |yes | ENV | -e | yes |no ### ADD VS COPY 1. 都可以將本地資料添加到鏡像的檔案中。 2. 唯一差別是ADD可以複製url中的檔案,複製到image,算是加強版的COPY。 ```docker= COPY <src>… <dest> ADD git@git.example.com:foo/bar.git /bar ``` | COPY | 本地 | | -------- | -------- | | ADD | 拉取遠端解壓 | ## CMD 1.cmd就是告訴正在run的container執行的指令。 2.如果在run image後面沒有加command的option,預設就會執行dockerFile中的CMD結果。 3.如果在run image時加了command就會覆蓋dockerFile CMD選項。 ```docker= FROM scratch ADD alpine-minirootfs-3.12.0-x86_64.tar.gz / CMD ["/bin/sh"] ``` 以上的許法等同於 ``` docker run -it scratch /bin/sh ``` ## ENTRYPOINT 1.與CMD一樣可以在image中run。 2.ENTRYPOINT可以透過CMD當作參數傳遞。 **以下三種寫法等效 :** ```docker = // 第一種,用 ENTRYPOINT,和用 CMD ENTRYPOINT ["/bin/cat"] CMD ["/etc/passwd"] // 第二種,只用 ENTRYPOINT,不用 CMD ENTRYPOINT ["/bin/cat", "/etc/passwd"] // 第三種,不用 ENTRYPOINT,只用 CMD CMD ["/bin/cat", "/etc/passwd"] ``` 都是在image中run /bin/cat /etc/passwd **備註** :當ENTRYPOINT與CMD混用時,官方退件採用Exec form去寫,這時ENTRYPOINT主要的就當作著要執行指令開頭,例如ENTRYPOINT ["echo"],CMD此時就當作參數指定下去 ```docker= ENTRYPOINT ["echo"] CMD ["123", "456"] ``` 執行結果 ``` ->123 456 ``` | | build | run |權重 | -------- | -------- | -------- |----- | RUN | 前 | no |自己 | CMD | 後 | yes |低 | ENTRYPOINT | 後 | yes |高 ## ENTRYPOINT demo `ENTRYPOINT ` 的出現其實是會了整合所有 `command` 到一個 `sh` 中去執行 假設我自己包的 `image` 需要起一個 `db migrate` 的功能後接著去跑 `test` 所以我就寫一個 `run-integration.sh` 的 `script` 。 ```typescript #!/usr/bin/env bash DIR="$(cd "$(dirname "$0")" && pwd)" source $DIR/setenv.sh echo '🟡 - Waiting for database to be ready...' $DIR/wait-for-it.sh "${DATABASE_URL}" -- echo '🟢 - Database is ready!' npx prisma migrate dev --name init if [ "$#" -eq "--ui" ] then vitest -c ./vitest.config.integration.ts --ui else vitest -c ./vitest.config.integration.ts fi ``` 所以 `dockerFile` 可以這麼寫 ```typescript FROM node:latest WORKDIR /usr/src/app COPY package*.json ./ RUN npm install COPY . . ENTRYPOINT ["run-integration.sh"] ``` 當然你也可以改成 `CMD` ```typescript FROM node:latest WORKDIR /usr/src/app COPY package*.json ./ RUN npm install COPY . . CMD ["run-integration.sh"] ``` 然後就可以自行打包成特定 `image` 日後使用 ```typescript docker build -t your-image-name . ``` 而每當你的 `service` 使用剛剛打包好的 `image` 那這個 `server` 預設就會去跑 `run-integration.sh` 的內容。 ```typescript version: '3.8' services: some_server: image: your-image-name restart: always ``` 但如果有一個情況是你的 `run-integration.sh` 需要傳參數怎麼辦,這時你可以搭配 `ENTRYPOINT` 跟 `CMD` 去使用。 仔細看一下這個 `sh` 有加參數。 ```typescript= #!/usr/bin/env bash // run-integration.sh // .. if [ "$#" -eq "--ui" ] then vitest -c ./vitest.config.integration.ts --ui else vitest -c ./vitest.config.integration.ts fi ``` `vitest` 中有提供 `--ui` 的 `arg` 讓你跑的 `test` 有 `UI` 呈現,所以我們再 `sh` 中可以透過 `$#` 表示我用 `sh` 時帶入的第一個參數,如果是判斷第二個參數就是 `$2` 以此類推,實際使用方式如下: ```typescript > ./run-integration.sh --ui ``` 因為 `if` 原因所以你會執行 `vitest -c ./vitest.config.integration.ts --ui` ```typescript if [ "$#" -eq "--ui" ] then vitest -c ./vitest.config.integration.ts --ui ``` 最後我們在改寫 `docker file`,這邊的 `CMD` 就可以充當 `ENTRYPOINT` 的參數放置的地方~ ```typescript // dockerfiles/Dockerfile.test FROM node:latest WORKDIR /usr/src/app COPY package*.json ./ RUN npm install COPY . . ENTRYPOINT ["run-integration.sh"] CMD ["--ui"] ``` ```typescript // dockerfiles/Dockerfile.test_ui FROM node:latest WORKDIR /usr/src/app COPY package*.json ./ RUN npm install COPY . . ENTRYPOINT ["run-integration.sh"] CMD [""] ``` 這樣我們就可以根據不同的 `CMD` 的參數 `build` 出不同版本的 `image` ~ ```typescript > docker build -f dockerfiles/Dockerfile.test -t myapp_test . > docker build -f dockerfiles/Dockerfile.test_ui -t myapp_test_ui . ``` ### build `docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .` **`-t`** : 指定tag,冒號後面是版本號 ### show build log `docker build --no-cache --progress=plain -t my-image .` ### export ` docker export [containerid] > 123.rar ` ### import ` cat 123.tar | docker import - [usr]/[imageid]:[version]` ### customer a specific build stage ```docker= FROM node:lts as lts COPY . ./app WORKDIR /app RUN npm i EXPOSE 3001 CMD npm run dev FROM node:lts-alpine as lts-alpine COPY . ./app WORKDIR /app RUN npm i EXPOSE 3001 CMD npm run dev ``` `docker build --target lts -t alexellis2/href-counter:latest .` `--target` : 指定dockerfile需要建構的執行內容 **build-stage**可以讓dockerfile的build流程脈絡更直觀 ## redis ### 開啟redis永久化資料庫,並指定權限密碼 `docker run --name my_redis -dp 6379:6379 -v V_redis_data:/data redis redis-server --appendonly yes --requirepass 123` **`--name`** : 指定container name **`-v`** :指定volumne連結 ------------------------ ### 連結redis config 到本地端,透過本地redis.conf修改映射到container中 ```docker= docker run --name my_redis -dp 6379:6379 \ -v V_redis_data:/data \ -v ~/mydata/redis/conf/redis.conf:/etc/redis/redis.conf \ redis redis-server /etc/redis/redis.conf ``` **備註** :當使用config黨時記得加 **redis-server** /etc/redis/redis.conf,讓server吃到config檔案 ### redis Dockerfile ```docker= FROM redis COPY redis.conf /usr/local/etc/redis/redis.conf EXPOSE 6379 CMD ["redis-server","/usr/local/etc/redis/redis.conf"] ``` ### node Dockerfile ```docker= FROM node:lts COPY . ./app WORKDIR /app RUN npm i EXPOSE 3001 CMD npm run dev ``` ### rect with nginx Dockerfile ```docker= ARG version FROM nginx:${version} AS builder WORKDIR /app COPY ./build . FROM nginx:${version} WORKDIR /app COPY --from=builder /app/ /usr/share/nginx/html EXPOSE 80 ``` #### builder step `--from=builder ` : 預設`--from=0`可以讓docker build指定順序 #### ARG變數指定 `docker build --build-arg version=alpine` ----------------- **進入image容器** `docker exec -it 0b04de2179c0 bash` **bash是在container中要使用的CMD工具預設是bash** `-i` 交互式操作 `-t` 偽終端 **進入redis資料庫** `redis-cli` **指令redis的host跟p號,兩者都需指定,不然就會是預設 0.0.0.0:6379** `redis-cli -h localhost -p 6380` **權限進入redis** `auth 123 ` **備註** : redis.conf中設定requirepass 123 [官方預設config](https://redis.io/docs/manual/config/) ### mac 查看本地PORT運行狀況 ` lsof -n -i:[my_port]` ### mac 查看當前資料夾絕對路徑 `echo ${PWD}` ### docker 查看鏡像PORT運行狀況 ` docker top [containerId]` **刪除所有未使用的container、network、image(包括懸空的和未引用的)以及volumn。** ``` docker system prune -a ``` ## docker-compose 讓docker可以運行多個container實例的工具,同時幫你自動生成docker image。 ```docker= version: "3.0" services: react-app: stdin_open: true tty: true build: . ports: - "3000:3000" volumes: - ${PWD}/src:/app_customer/src environment: - REACT_APP_NAME=danny_compose - CHOKIDAR_USEPOLLING=true # env_file: # - .env ``` 以上的參數會對應docker run的指令 `version` :指定compose版本 `services` :compose中需要加載的服務,也就是要run哪些image ### h2啟動compose ``` docker-compose up ``` 這時compose會幫你做兩件事 1.build 一個image 2.生成container ```cmd REPOSITORY TAG IMAGE ID CREATED SIZE react_docker_react-app latest 0bea35c3f673 54 seconds ago 851MB ``` **compose被根據services name + file name去命名REPOSITORY** 但每次docker-compose up 預設只會build一個image防止生成太多,如果需要新增不同image版本需要加--build flag 例如 : `docker-compose up --build ` 也可以指定不同的docker-compose檔名去複寫ymal,所以也可以根據不同環境去compse不同image版本去做測試 : ``` docker-compose -f docker-compose.yml -f docker-compose-dev.yml up --build ``` 查看compose檔案編寫情況 `docker compose config -q` ## Exec form vs. Shell form ```docker= 1. Shell form command param1 param2 2.Exec form ["executable","param1","param2"] ``` 以上是docker寫command格式 ### docker build 改變log形式成plain預設是auto ``` docker build --no-cache --progress=plain -t my-image . ``` --progress docker build 進程方式 --no-cache :取消docker 緩存讓每一次重新打包 ## 概念 ### 鏡像加載 1.docker鏡像是一組一組文件系統產生(UnionFS)。 2.bootfs(boot file system) + kernel(簡易版liunx系統)組成。 3.kernel引導docker下載linux內和系統。 4.一層一層文件系統產生docker 實例。 5.rootfs(root file system)會在container中生成/etc、/dev等文件資料夾。 6.docker的linux有不同種類的liunx系統,每個系統只安裝特定的套件,這也是為什麼docker的linux系統檔案體積小的原因。 7.docker image採用分層鏡像,目的在於複用、加載簡單。 ### 分層 1.容器曾底下多個鏡像層。 2.鏡像層只可讀、容器可讀可寫。 ### docker --privileged=true 参数作用 1. 使用該參數,container內的root擁有真正的root權限。 1. 否則,container內的root只是外部的一個普通用戶權限。 1. privileged啟動的容器,可以看到很多host上的設備,並且可以執行mount。 1. 甚至允許你在docker容器中啟動docker容器。 ### docker 監控 dockerlazloading 1. docker stats -v ${PWD}:/app -v /app/node_modules 會這樣volumn的原因是,如果沒有-v /app/node_modules,當本地的node_modules刪除,docker container中的node_modules也會刪除,所以才會新增-v /app/node_modules讓docker新建一個volumn去連結container的node_modules 。 ```docker= ## > docker run -v ${PWD}:/app -v /app/node_modules -dp 3000:3000 my_node:v1.0.2 ```