Docker
- 【上課筆記】【hiskio】Docker 完全實作!結合 3 大語言掌握容器實務應用
- 【上課筆記】【udemy】 Docker容器技术从入门到精通
- 【Demo】Python Flask + Redis 練習
- 【Docker】【錯誤處理】無法停止服務
- 【Webmin】相關
- Docker 各系統安裝
- Docker 安全性
- Docker 相關連結
- Docker 部署IIS
- Docker 監控相關
- Docker 儲存
- Docker Compose
- Docker container 常用軟體安裝
- Docker File
- Docker Image 相關
- Docker Image Server DockerRegistry
- Docker Image Server nexus
- Docker Network
- Docker Swarm
- Docker如何固定IP設定的方法
- Docker安裝
- Docker架構
- Docker常用指令
- Dockerfile 的 /etc/hosts 如何設定
- Docker 使用私有repo (Nexus)
- Docker 內 container 如何使用Docker
- 【Docker】上傳自己的 Docker image 到 Docker Hub
- 【Docker】自動分配網路用盡
- 【Docker】image tage 比較
【上課筆記】【hiskio】Docker 完全實作!結合 3 大語言掌握容器實務應用
上課講義
nocopy : 存在檔案不覆蓋
【上課筆記】【udemy】 Docker容器技术从入门到精通
上課講義
原始碼
【Demo】Python Flask + Redis 練習
準備一個python文件 app.py
from flask import Flask
from redis import Redis
import os
import socket
app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)
@app.route('/')
def hello():
redis.incr('hits')
return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"
準備一個Dockerfile
FROM python:3.9.5-slim
RUN pip install flask redis && \
groupadd -r flask && useradd -r -g flask flask && \
mkdir /src && \
chown -R flask:flask /src
USER flask
COPY app.py /src/app.py
WORKDIR /src
ENV FLASK_APP=app.py REDIS_HOST=redis
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
準備一個Redis image
$ docker image pull redis
$ docker image build -t flask-demo .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-demo latest 4778411a24c5 About a minute ago 126MB
python 3.9.5-slim c71955050276 8 days ago 115MB
redis latest 08502081bff6 2 weeks ago 105MB
建立一Bridage
$ docker network create -d bridge demo-network
8005f4348c44ffe3cdcbbda165beea2b0cb520179d3745b24e8f9e05a3e6456d
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
2a464c0b8ec7 bridge bridge local
8005f4348c44 demo-network bridge local
80b63f711a37 host host local
fae746a75be1 none null local
創建container
創建一個叫 redis-server 的container,連到 demo-network上
$ docker container run -d --name redis-server --network demo-network redis
002800c265020310231d689e6fd35bc084a0fa015e8b0a3174aa2c5e29824c0e
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
002800c26502 redis "docker-entrypoint.s…" 4 seconds ago Up 3 seconds 6379/tcp redis-server
創建 flask container,連到 demo-network上
$ docker container run -d --network demo-network \
--name flask-demo \
--env REDIS_HOST=redis-server \
-p 5000:5000 flask-demo
打開瀏覽器訪問 http://127.0.0.1:5000
應該能看到類似下面的內容,每次刷新頁面,計數加1
Hello Container World! I have been seen 36 times and my hostname is 925ecb8d111a.
合併步驟成一腳本
# prepare image
docker image pull redis
docker image build -t flask-demo .
# create network
docker network create -d bridge demo-network
# create container
docker container run -d --name redis-server --network demo-network redis
docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo
【Docker】【錯誤處理】無法停止服務
*使用systemctl stop docker 無法停止服務
[root@localhost docker]# systemctl stop docker
Warning: Stopping docker.service, but it can still be activated by:
docker.socket
*解決
#停止
[root@localhost docker]# systemctl stop docker.socket
[root@localhost docker]# systemctl stop docker.service
#啟動
[root@localhost docker]# systemctl daemon-reload
[root@localhost docker]# systemctl restart docker
**如果還沒辦法解決
# 删除 docker socket 文件
cd /lib/systemd/system/ && rm -rf docker.socket
# 查看 docker 状态,已dead
sudo systemctl status docker.service
# 重新创建 docker.socket
cd /lib/systemd/system/
vim docker.socket
# 添加如下内容
[Unit]
Description=Docker Socket for the API
PartOf=docker.service
[Socket]
ListenStream=/var/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker
[Install]
WantedBy=sockets.target
#啟動
[root@localhost docker]# systemctl daemon-reload
[root@localhost docker]# systemctl restart docker
【Webmin】相關
查詢被Deny 需要從Zone default -> Allow queries form ... -> 【 Listed..】-> 加入 any -> 右上角 【 Apply 】
Docker 各系統安裝
各系統安裝連結
- zabbix
https://www.zabbix.com/documentation/4.4/zh/manual/installation/containers
https://github.com/zabbix/zabbix-docker/tree/6.4/Dockerfiles/web-nginx-pgsql - elkhttps://github.com/deviantony/docker-elk
https://github.com/deviantony/docker-elk - smokeping
https://cloud.tencent.com/developer/article/1703797 - jenkins
https://hub.docker.com/_/jenkins/ - wekan
https://github.com/wekan/wekan/wiki/Docker
https://wekan.github.io/wekan-doc/ (文件) - postgresSQL
https://hub.docker.com/_/postgres
https://cde566.medium.com/docker-compose%E5%89%B5%E5%BB%BApostgresql-7f3f9519fa20 - powerdns
https://blog.csdn.net/sdhzdtwhm/article/details/135910057
Jenkins
Jenkins - Official Image | Docker Hub
jenkins/jenkins - Docker Image | Docker Hub
docker run -d \
-p 8080:8080 -p 50000:50000 \
-v ~/jenkins/home:/var/jenkins_home \
--name jenkins \
jenkins/jenkins:lts
ps. jenkins 抓取一定要帶版號不然會報錯
Using default tag: latest
Error response from daemon: manifest for jenkins:latest not found: manifest unknown: manifest unknown
docker pull jenkins/jenkins:lts
ps. 要安裝軟體需要使用root 登入
docker exec -u root -it jenkins sh
touch: cannot touch ‘/var/jenkins_home/copy_reference_file.log’: Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
sudo chown -R 1000:1000 {mount jenkins home path}
ps.使用nginx revers proxy 現設定問題,需要設定 proxy_set_header
Reverse proxy configuration (jenkins.io)
server {
listen 80;
server_name tomcat.srou.com;
location / {
sendfile off;
proxy_pass http://192.168.100.4:10031/;
proxy_redirect default;
proxy_http_version 1.1;
# Required for Jenkins websocket agents
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_max_temp_file_size 0;
#this is the maximum upload size
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffering off;
proxy_request_buffering off; # Required for HTTP CLI commands
proxy_set_header Connection ""; # Clear for keepalive
}
}
gitlab
docker run -d \
--name gitlab \
-p 8080:80 -p 443:443 -p 22:22 \
--privileged \
--restart always \
-v ~/gitlab/config:/etc/gitlab \
-v ~/gitlab/logs:/var/log/gitlab \
-v ~/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce
-d : 讓容器(Container)在背景執行
--name : 指定容器(Container)的名稱
-p : 指定主端(Host)對應客端(Quest)的連接埠
--privileged : 容器(Container)内的 root 拥有真正的 root 权限
-restart : 設定為 restart,則容器在異常停止後,會自動重新起動
-v : 將容器(Container)內需永續保存的目錄,連結至主端的目錄
docker-compose.yml
docker-compose up -d
version: "3.6"
services:
gitlab:
image: gitlab/gitlab-ce:latest
restart: always
privileged: true
ports:
- '8080:80'
- '443:443'
- '22:22'
volumes:
- '~/gitlab/data:/var/opt/gitlab'
- '~/gitlab/logs:/var/log/gitlab'
- '~/gitlab/config:/etc/gitlab'
Tomcat
Docker部署tomcat出現404錯誤 - 台部落 (twblogs.net)
新版tomcat 不提供manager ,需手動複製,webapps.dist copy -> webapps
PostgresSQL
version: "3.7"
services:
db:
image: postgres:13.2-alpine
restart: always
environment:
POSTGRES_DB: postgres
POSTGRES_USER: $USER_NAME #postgres
POSTGRES_PASSWORD: $PASSWORD #1234
PGDATA: /var/lib/postgresql/data
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "5432:5432"
pgadmin:
image: dpage/pgadmin4:latest
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: $USER_EMAIL #xxx@gmail
PGADMIN_DEFAULT_PASSWORD: $PASSWORD #abcd
PGADMIN_LISTEN_PORT: 80
ports:
- "8080:80"
volumes:
- pgadmin-data:/var/lib/pgadmin
links:
- "db:pgsql-server"
volumes:
db-data:
pgadmin-data:
Docker 安全性
為何在 Docker 中執行特權容器不是個好主意? – 資安趨勢部落格 (trendmicro.com.tw)
Docker 相關連結
docker 測試環境(play with docker)
Play with Docker (play-with-docker.com)
【上課講義】【udemy】 Docker容器技术从入门到精通
【上課講義】【hiskio】Docker 完全實作!結合 3 大語言掌握容器實務應用
【鐵人賽】用30天來介紹和使用 Docker
用30天來介紹和使用 Docker :: 2018 iT 邦幫忙鐵人賽 (ithome.com.tw)
Docker File 官方說明
Dockerfile reference | Docker Documentation
Spring Boot with Docker
Getting Started | Spring Boot with Docker
Docker 從入門到實踐
前言 - 《Docker —— 從入門到實踐》正體中文版 (gitbook.io)
Docker 與 JAVA開發實戰-程序员欣宸
https://github.com/zq2599/blog_demos
各系統安裝連結
- zabbix
https://www.zabbix.com/documentation/4.4/zh/manual/installation/containers
https://github.com/zabbix/zabbix-docker/tree/6.4/Dockerfiles/web-nginx-pgsql - elk
- smokeping
https://cloud.tencent.com/developer/article/1703797 - jenkins
https://hub.docker.com/_/jenkins/
Docker 部署IIS
- [Docker] 容器初體驗 - 搞個 IIS Container 來部署網站 | 搞搞就懂 - 點部落 (dotblogs.com.tw)
- 將 ASP.NET MVC 應用程式遷移到 Windows 容器 | Microsoft Learn
- 準備 Windows 作業系統容器 | Microsoft Learn
Docker 監控相關
cadvisor 監控主機狀況
# 使用cadvisor
$ docker run -d --restart=always -v /var/run:/var/run:rw -p 8080:8080 --name=cadvisor google/cadvisor:latest
netdata 監控主機狀況
Install Netdata with Docker | Learn Netdata
docker run -d --name=netdata \
-p 19999:19999 \
-v netdataconfig:/etc/netdata \
-v netdatalib:/var/lib/netdata \
-v netdatacache:/var/cache/netdata \
-v /etc/passwd:/host/etc/passwd:ro \
-v /etc/group:/host/etc/group:ro \
-v /proc:/host/proc:ro \
-v /sys:/host/sys:ro \
-v /etc/os-release:/host/etc/os-release:ro \
--restart unless-stopped \
--cap-add SYS_PTRACE \
--security-opt apparmor=unconfined \
netdata/netdata
Docker 儲存
--mount 與 -v/ --volume
當在使用 docker create 或是 docker run 的指令時,會有兩個選項可以使用,個別是 --mount 以及 -v (或是 --volume)。
注意,千萬不要誤以為使用 -v 就是用 Volume 的方式或者是 --mount 就是使用 Bind Mount 的方式,不是這樣的!!我們先來看看這兩個在 docker create 與 docker run 中的說明是甚麼:
-v, --volume list Bind mount a volume
--mount mount Attach a filesystem mount to the container
但其實是可以用 --mount 或 -v 來綁定資料到 Volume,也可以用 --mount 或 -v 來綁定資料到 Filesystem。既然都可以,那為什麼要分兩種呢?
這是因為 --mount 其實出現的比較早,但他設定綁定資料的方式比較完整卻麻煩,因此在後續又出現了 -v 的選項來簡化綁定的設定過程。
Data Volume
相關指令
# 建立volume
# docker volume create [OPTIONS] [VOLUME]
$docker volume create my-vol
#使用 -v
$ docker run -d --name my-http -v my-vol:/usr/local/apache2 -p 8080:80 httpd
#使用 --mount
$ docker run -d --name my-http --mount source=my-vol,target=/usr/local/apache2 -p 8080:80 httpd
# 列出所有volume
[tomcat@docker]$ docker volume ls
DRIVER VOLUME NAME
local 2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8
local my-vol
# 查詢某volume細節
[tomcat@docke]$ docker volume inspect 2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8
[
{
"CreatedAt": "2019-06-04T15:40:43+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8/_data",
"Name": "2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8",
"Options": null,
"Scope": "local"
}
]
[tomcat@docke]$ docker volume inspect my-vol
[
{
"CreatedAt": "2023-02-07T02:11:18Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
# 刪除所有不使用的volume
[tomcat@docker]$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
2db2d2b1a6ceb9f578d2149dc44625e1996dcc33de0639f8644ce2082182b9a8
44d6286d1a793ac8c788b0fd364fa8d9430ce3ec079731dbe894dd996cae9adb
925559f4c2399c0c989d8602ca77fb3243dfb83022fe029b09b130c9521e7f89
9484acbd73c53af47f29add893c5125b0cd7f0f96e521c54e643f1aeb8f97eeb
b47202da45060de8f67e48058966b32078d525cadd1c94671a0ffe33f3d5df4b
006d620ae77bf7530ea37abd48b9c20ac264956f4b9ef3397e2c5072f32677c2
15e0fd2e575c2c24a2a137ff420dbf30326f3859d483561efc45e568d294c802
98b5385b04a401e9e870c5a1e807d69c56a1764b2ec91baedd9c13994cb2c863
ffdae0e84d00966afdc0df55d664d06ee99c14d249ad7558848088c464d472ff
Total reclaimed space: 77.8MB
# 指定volume mount 至 /var/www/html
[tomcat@docker]$ docker run -d -v html_doc:/var/www/html httpd
Bind Mount to FileSystem
# 直接mount 外部資料夾 至 /var/www/html
# 相對路徑
[tomcat@docker-qc html-doc]$ docker run -d -v ${PWD}/html-doc:/var/www/html httpd
# 絕對路徑
[tomcat@docker-qc html-doc]$ docker run -d -v /home/tomcat/docker/html-doc:/var/www/html httpd
Bind Mount to Memory
Tmpfs,全名 Temporary File System,是在 Linux 暫存檔儲存空間的常見名稱,通常以掛載檔案系統方式實現。他可以將資料儲存在記憶體中而不是存在硬碟內,所以只要一重新開機,資料則會被抹除。
Tmpfs 如其名,如果使用 Tmpfs Mount 則資料會存在於記憶體上,在重啟 Container 的時候就會 Reset 資料。
特別注意的是,使用 Tmpfs Mount 並不是使用 -v 的選項,而是使用 --tmpfs ,--tmpfs 就如同 -v一樣簡化了使用 --mount 時所需的設定。
使用 --tmpfs
$ docker run -d --name my-http --tmpfs /usr/local/apache2/htdocs -p 8080:80 httpd
使用 --mount
$ docker run -d --name my-http --mount type=tmpfs,destination=/usr/local/apache2/htdocs -p 8080:80 httpd
官方参考链接 https://docs.docker.com/storage/volumes/#share-data-among-machines
常用volume mapping (log)
# nginx
docker run -p 80:80 -itd -v /tmp/log/nginx/docker-nginx-log:/var/log/nginx nginx
使用綁定的不同時機比較
既然 Docker 提供了這麼多的方式讓我們儲存資料,那麼我們應該在甚麼情況下使用哪一種方式來儲存呢?
- 使用 Volume 的時機
- 想要在多個 Container 中共用資料的時候
- 在無法確保存放資料路徑或結構的時候
- 當 Docker 不在本機端而是在雲端或遠端的時候
- 當有需要資料的備份、還原、轉移的時候
- 當資料有大量 I/O 的情況,Docker 建議你可以把資料放在 Docker VM 內,會有更好的性能表現
- 當你的應用程式需要完整且原生的 File System 的時候
- 使用 Bind Mount 的時機
- 想要將 Config 設定檔同時分享給 Container 與本機端的時候
- 想要同時分享程式碼或其他來源文件給 Container 與本機端的時候
- 當非常確定所綁定的路徑底下,其資料夾與結構等等都非常確定且保持一致不會改變的時候
- 使用 Tmpfs Mount 的時機
- 不想讓資料被永久儲存的時候
- 需要每次重啟都有乾淨的環境時
- 當想要讀寫性能非常好時,因為它掛載的空間是在記憶體上
Docker筆記 - 讓資料遠離Container,使用 Volume、Bind Mount 與 Tmpfs Mount
Docker Compose
安裝
Install Docker Compose | Docker Documentation
# linux
# 到這裡找版號 https://github.com/docker/compose/releases
# sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
$ sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c
# 找不到命令的可以建立軟連結
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
熟悉python的朋友,可以使用pip去安装docker-Compose
$ pip install docker-compose
YML 語法結構
compose-spec/spec.md at master · compose-spec/compose-spec · GitHub
version: "3.8"
services: # 容器
servicename: # 服務名字,這個名字也是內部 bridge網絡可以使用的 DNS name
container_name: #自定義container 名稱,不使用會用 servicename
# build: ./buildfolder
build: # 加此參數會使用docker file 建立
# 如果要指定 dockerfile 使用 context + dockerfile
#build:
# context: ./buildfolder
# dockerfile: Dockerfile.dev
image: # 鏡像的名字(如果使用build,此選項會變成image名稱,如果沒有build,會從網路拉取該名稱image)
command: # 可選,如果設置,則會覆蓋默認鏡像裡的 CMD命令, ex: /bin/sh -c “while true;do sleep 3600 ;done”
environment: # 可選,相當於 docker run裡的 --env
volumes: # 可選,相當於docker run裡的 -v
networks: # 可選,相當於 docker run裡的 --network
ports: # 可選,相當於 docker run裡的 -p
servicename2: #第二個服務
volumes: # 可選,相當於 docker volume create
networks: # 可選,相當於 docker network create
範例
version: "3.8"
services:
flask-demo:
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
- TZ=Asia/Taipei #設定時區
networks:
- demo-network
ports:
- 8080:5000
volumes:
- ./config/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro,Z
- ./config/logstash/pipeline:/usr/share/logstash/pipeline:ro,Z #ro: read only, Z: 在SElinux 下唯讀(啟用SEliunx才有用)
redis-server:
image: redis:latest
networks:
- demo-network
networks:
demo-network:
docker-compose 與 docker 版本對應,語法說明
https://docs.docker.com/compose/compose-file/
docker run 轉換 docker-compose 語法工具
https://www.composerize.com/
常用指令
# 前景執行
docker-compose up
# 背景執行
docker-compose up -d
# 使用自定義前贅字(預設是用資料夾名稱)
docker-compose -p my_project up -d
# 停止docker-compose
docker-compose stop
# 移除docker-compose
docker-compose down
# 移除docker-compose 同時移除volumn
docker-comopose down -v
# 列出container
docker-compose ps
# 列出log
docker-compose log
docker-compose log -f
# 在webapp 內執行sh
docker-compose exec webapp sh
# 使用top監控
docker-compose top
# 抓取(build)image
docker-compose pull
更新服務
當程式有異動,想要重新build image
# 重新build image
docker-compose build
# 啟動時重新build
docker-compose up --build
# 如果有服務被移除,使用 --remove-orphans 移除相關資源
docker-composer up --remove-orphans
# 如果有變更掛載,需要重啟服務
docker-composer restart
網路配置
networks:
mynetwork1:
ipam:
driver: default
config:
- subnet: 172.28.0.0/16
mynetwork2:
ipam:
driver: default
config:
- subnet: 172.29.0.0/16
清除環境
docker-compose stop
docker-compose rm
docker system prund -f
docker-compose volume 寫法
docker compose yml 範例
version: "3"
services:
webapp:
#指定image
image: httpd:alpine
ports:
- "80:80"
volumes:
- "/data"
version: "3"
services:
webapp:
#指定image
build:
context: .
dockerfile: Dockerfile-alternate
args:
buildno: 1
相關連結
- Docker 教學課程-第8部分:使用 Docker Compose | Microsoft Docs
- Compose file version 3 reference | Docker Documentation
- Overview of docker-compose CLI | Docker Documentation
- 官網文件 https://docs.docker.com/compose/
Docker container 常用軟體安裝
更新 apt-get
#更新apt-update
apt-get update
vim
sudo apt-get install -y vim
ip
sudo apt-get install -y iproute2
ping
sudo apt-get install -y iputils-ping
ifconfig, netestat
sudo apt-get install -y net-tools
nc (netcat)
sudo apt-get install -y netcat
maven
FROM jenkins/jenkins:lts
RUN apt-get update && apt-get install -y maven
apt-get 使用proxy
#apt_proxy.conf 設定 proxy
Acquire::http::proxy "http://127.0.0.1:8000/";
Acquire::ftp::proxy "ftp://127.0.0.1:8000/";
Acquire::https::proxy "https://127.0.0.1:8000/";
#可以用“-c”選項來指定使用配置檔案
sudo apt-get -c ~/apt_proxy.conf update
#apt-get也有一個“-o”選項,直接跟apt-get的設定變數,就不用指定配置檔案了,比如
sudo apt-get -o Acquire::http::proxy="http://127.0.0.1:8000/" update
Docker File
Docker File 官方說明
Dockerfile reference | Docker Documentation
官方各類型docker file 專案連結(可找到專案git repo)
official-images/library at master · docker-library/official-images · GitHub
Build Dockerfile
# docker image build -t {name}:{tag} {dokerfile 目錄}
docker image build -t hello_py:v1 .
Docker File 結構
# 基礎鏡像檔
FROM ubuntu:21.04
# 環境變數
ENV VERSION=2.0.1
# 執行命令(可在執行容器的時候使用其他命覆蓋)
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
# 移動到當前目錄
WORKDIR /app
# 複製檔案
COPY hello.py /app/hello.py
# 複製檔案(如果是壓縮檔會自行解壓縮,一般來說還是用COPY)
ADD hello.tar.gz /app/
# 執行命令(無法覆蓋)
ENTRYPOINT ["echo", "hello docker"]
Docker Image 相關
鏡像的獲取
-
pull from
registry(online) 從registry拉取-
public(公有)
-
private(私有)
-
-
build from
Dockerfile(online) 從Dockerfile構建 -
load from
file(offline) 文件導入 (離線)
官方image:
https://hub.docker.com/search?type=image&image_filter=official
常用 public image server
*建立映像檔
# 於當前目錄,按Dockerfile.test中的指令,建立test:v1 ({name:tag})的映像檔
docker build . -f Dockrfile.test -t test:v1
*拉取與推送映像檔
# 使用docker hub 下載image ({name:tag})
docker pull node:alpine
# 使用quay.io 下載image
docker pull quay.io/bitnami/tomcat
# node:alpine 重新命名標籤 username/nodea:alpine
docker tag node:alpine username/nodea:alpine
# 推送至私人倉庫
docker push username/node:alpine
使用私有registry
# 修改鏡像registry
sudo vim /etc/docker/daemon.json
#########################################################
# 可與許http鏡像 "insecure-registries" : ["10.60.78.79"],
# 鏡像站 "registry-mirrors": ["http://hub-mirror.c.163.com"]
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}
#########################################################
# 使文件生效
sudo systemctl daemon-reload
# 重啟docker
sudo service docker restart
使用proxy 抓取鏡像檔
*ubuntu
# 編輯設定檔
vim /etc/default/docker
##檔案內容
export http_proxy="http://192.168.33.10:3128/"
export https_proxy="http://192.168.33.10:3128/"
##
# proxy 如果有帳密驗證
export http_proxy="https://username:password@192.168.33.10:3128/"
# 重啟docker
systemctl restart docker
*centos
vim /etc/systemd/system/docker.service.d/http-proxy.conf
###################################################################################
[Service]
Environment="HTTP_PROXY=http://192.168.1.1:3128/"
Environment="HTTPS_PROXY=http://192.168.1.1:3128/"
#如果要排除不走
Environment="NO_PROXY=localhost,127.0.0.0/8,docker-registry.somecorporation.com"
###################################################################################
#重讀設定
systemctl daemon-reload
#重啟docker
systemctl restart docker
查看image
# 取得id
[root@localhost ~]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat latest 9f35737a8466 30 hours ago 680MB
busybox latest beae173ccac6 3 weeks ago 1.24MB
openjdk 8-jre-alpine f7a292bbb70c 2 years ago 84.9MB
[root@localhost ~]# docker image inspect f7a292bbb70c
[
{
"Id": "sha256:f7a292bbb70c4ce57f7704cc03eb09e299de9da19013b084f138154421918cb4",
"RepoTags": [
"openjdk:8-jre-alpine"
],
"RepoDigests": [
"openjdk@sha256:f362b165b870ef129cbe730
.......
# 重要訊息
"Id": "sha256:f7a292bbb70c4ce57f7704cc03eb09e299de9da19013b084f138154421918cb4",
"RepoTags": [
"openjdk:8-jre-alpine"
],
"Architecture": "amd64",
"Os": "linux",
刪除 image
# 查詢要刪除imge id
[root@localhost ~]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat latest 9f35737a8466 30 hours ago 680MB
busybox latest beae173ccac6 3 weeks ago 1.24MB
openjdk 8-jre-alpine f7a292bbb70c 2 years ago 84.9MB
# 刪除 image
[root@localhost ~]# docker image rm beae173ccac6
Untagged: busybox:latest
Untagged: busybox@sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678
Deleted: sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a
Deleted: sha256:01fd6df81c8ec7dd24bbbd72342671f41813f992999a3471b9d9cbc44ad88374
# 刪除名字跟nginx 有關的imge (-f 強制)
[root@localhost ~]# docker images | grep nginx | awk '{print $3}' | xargs docker rmi
[root@localhost ~]# docker images | grep nginx | awk '{print $3}' | xargs docker rmi -f
# 刪除沒有名字的image (-f 強制)
[root@localhost ~]# docker images | grep "<none>" | awk '{print $3}' | xargs docker rmi
[root@localhost ~]# docker images | grep "<none>" | awk '{print $3}' | xargs docker rmi -f
image 匯入匯出
# 查看 image
[root@localhost ~]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat latest 9f35737a8466 30 hours ago 680MB
busybox latest beae173ccac6 3 weeks ago 1.24MB
openjdk 8-jre-alpine f7a292bbb70c 2 years ago 84.9MB
# 匯出 image
# docker image save {name}:{tag} -o {匯出檔名}
[root@localhost ~]# docker image save openjdk:8-jre-alpine -o openjdk:8-jre-alpine.image
# 查看是否匯出
[root@localhost ~]# ls openjdk\:8-jre-alpine.image
openjdk:8-jre-alpine.image
# image 匯入
# docker image load -i {image file}
[root@localhost ~]# docker image load -i ./openjdk\:8-jre-alpine.image
f1b5933fe4b5: Loading layer [==================================================>] 5.796MB/5.796MB
9b9b7f3d56a0: Loading layer [==================================================>] 3.584kB/3.584kB
edd61588d126: Loading layer [==================================================>] 80.28MB/80.28MB
Loaded image: openjdk:8-jre-alpine
push image to docker hub
# 建立上傳image
#docker image tag nginx {docker hub id}/{name}:{tag}
docker image tag nginx treemanou/my_nginx:v2
#docker image push {docker hub id}/{name}:{tag}
docker image push treemanou/my_nginx:v2
commit 建立 image
# 找一個conatiner
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e09bb406dcef busybox "sh" 53 seconds ago Exited (0) 18 seconds ago hardcore_bartik
# 利用此container建立 image
# docker commit {container id} {name}:{tag}
$ docker commit e09 treemanou/my_busybox:v3
sha256:42bc77669110af56336ec10afdbd7f92fa371f02ecccbda1e0bbb1bbc00f3cf6
Docker File
Docker File 官方說明
Dockerfile reference | Docker Documentation
*ENV 環境變數
ENV ANT_HOME /opt/ant
*ARG傳入參數
FROM alpine
ARG NODE_ARG
ENV NODE_ENV="${NODE_ARG:-development}"
RUN echo "ARG=${NODE_ARG}, ENV=${NODE_ENV}"
CMD ehco "ARG=${NODE_ARG}, ENV=${NODE_ENV}"
# 傳入NODE_ARG
# DOCKER_BUILDKIT=0
DOCKER_BUILDKIT=0 docker build --build-arg NODE_ARG=staging .
*工作目錄 WORKDIR
# / 下
RUN echo "say hi" > hi.txt
WORKDIR /app
# /app 下
RUN echo "hello" > world.txt
*指定用戶USER (不指定舊識)
# 新增 group與user
RUN groupadd -r redis && useradd -r -g redis redis
# 指定當前user為redis
USER redis
# 以redis的身份執行reidis-server
RUN ["redis-server"]
*Health Check
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
FROM registry.test.com.tw/test/centos:openjdk8_tomcat8.5
ENV CATALINA_HOME /opt/tomcat
ENV SERVICE_HOME /usr/AP/fubon
ENV ANT_HOME /opt/ant
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk
ENV PATH="${PATH}:${ANT_HOME}/bin:${CATALINA_HOME}/bin"
ENV AWS_HOME="/root/aws"
RUN wget https://archive.apache.org/dist/ant/binaries/apache-ant-1.10.7-bin.tar.gz \
&& tar -zxvf apache-ant-1.10.7-bin.tar.gz \
&& rm apache-ant-1.10.7-bin.tar.gz \
&& mv apache-ant* ${ANT_HOME} \
&& yum install -y python3 \
&& pip3 install awscli --upgrade --user \
&& ln -s /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el7_8.x86_64/ /usr/lib/jvm/java-1.8.0-openjdk.x86_64 \
&& yum install -y httpd php php-gd
COPY server.xml ${CATALINA_HOME}/conf/
COPY my_entrypoint.sh /my_entrypoint.sh
RUN chmod -v +x /my_entrypoint.sh
COPY --chown=user:group file* /app/
COPY server_builder.sh ${SERVICE_HOME}/
#Set Alias
RUN echo "alias cdcm='cd /usr/AP/test/cm'" >> ~/.bashrc \
&& echo "alias restartTomcat='cd /opt/tomcat/bin; ./shutdown.sh; /bin/sleep 3; ps -ef | grep '\''org.apache.catalina.startup.Bootstrap start'\'' | grep -v grep | awk '\''{print $2}'\'' | xargs kill; ./startup.sh'" >> ~/.bashrc \
&& echo "alias cmant='cd /usr/AP/test/cm/src; ant; restartTomcat'" >> ~/.bashrc \
EXPOSE 8080
CMD ["-D","FOREGROUND"]
# 預設目錄
WORKDIR /usr/AP/test/
# 一定會執行
ENTRYPOINT ["/my_entrypoint.sh"]
* 複寫CMD
docker run --rm apache:v1 -v
多階段建置
# 建置環境
FROM alpine as build
RUN echo "hello" > mytest
# 執行環境
FROM alpine
COPY --from=build /mytest .
RUN cat /mytest
Distroless Dock image => 精簡image
常用image
busybox
- 提供輕量簡單的shell 的 linux 容器
containous/whoami
- 提供返回ip hostname 服務
vagrant@swarm-manager:~$ curl 192.168.200.10:8080
Hostname: fdf7c1354507
IP: 127.0.0.1
IP: 10.0.0.7
IP: 172.18.0.3
IP: 10.0.1.14
RemoteAddr: 10.0.0.2:36828
GET / HTTP/1.1
Host: 192.168.200.10:8080
User-Agent: curl/7.68.0
Accept: */*
Docker Image Server DockerRegistry
Day7:建立 private 的 Docker Registry - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天 (ithome.com.tw)
$ docker run -d -p 5000:5000 -v /home/user1/storage:/var/lib/registry --name registry registry:2
#
# -d:執行的 docker container 是 run 在背景的狀態,所以需要使用 docker logs 的指令才可以看到 log 狀態
# -p:主機的 5000 port mapping 到 container 的 5000 port
# -v:因為 push 到 docker registry 的資料是放在 container 裡面的,如果把 docker container 刪除掉 docker registry 的 Image 資料就會不見,因此需要使用 –v 參數將主機的檔案路徑 mapping 到 container 裡面的檔案路徑,這樣 docker container 被刪除 docker registry 的 Image 資料還會存在
# --name:設定 docker container 的名稱
clinet / server 都要加 daemon.json
# vim /etc/docker daemon.json
# 允許192.168.1.1:5000 (port 一定要寫) 可不走https
{
"insecure-registries":["192.168.1.1:5000"]
}
上傳 image
# 假設本機ip 192.168.1.1 (registry server)
# 下載image
docker pull busybox
# 複製image 成 mybusybox
docker push tag busybox 192.168.1.1:5000/mybusybox
# 上傳image
docker pull 192.168.1.1:5000/mybusybox
下載 image
# 假設本機ip 192.168.1.2 (docker client)
docker pull docker pull 192.168.1.1:5000/mybusybox
Docker Image Server nexus
sonatype/nexus3 - Docker Image | Docker Hub
-
Use a docker volume. Since docker volumes are persistent, a volume can be created specifically for this purpose. This is the recommended approach.
$ docker volume create --name nexus-data $ docker run -d -p 8081:8081 --name nexus -v nexus-data:/nexus-data sonatype/nexus3 -
Mount a host directory as the volume. This is not portable, as it relies on the directory existing with correct permissions on the host. However it can be useful in certain situations where this volume needs to be assigned to certain specific underlying storage.
$ mkdir /some/dir/nexus-data && chown -R 200 /some/dir/nexus-data $ docker run -d -p 8081:8081 --name nexus -v /some/dir/nexus-data:/nexus-data sonatype/nexus3
Docker Network
建立兩容器
$ docker container run -d --rm --name box1 busybox /bin/sh -c "while true; do sleep 3600; done"
$ docker container run -d --rm --name box2 busybox /bin/sh -c "while true; do sleep 3600; done"
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4f3303c84e53 busybox "/bin/sh -c 'while t…" 49 minutes ago Up 49 minutes box2
03494b034694 busybox "/bin/sh -c 'while t…" 49 minutes ago Up 49 minutes box1
容器間的通信->使用bridge
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
1847e179a316 bridge bridge local
a647a4ad0b4f host host local
fbd81b56c009 none null local
$ docker network inspect bridge
[
{
"Name": "bridge",
"Id": "1847e179a316ee5219c951c2c21cf2c787d431d1ffb3ef621b8f0d1edd197b24",
"Created": "2021-07-01T15:28:09.265408946Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"03494b034694982fa085cc4052b6c7b8b9c046f9d5f85f30e3a9e716fad20741": {
"Name": "box1",
"EndpointID": "072160448becebb7c9c333dce9bbdf7601a92b1d3e7a5820b8b35976cf4fd6ff",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"4f3303c84e5391ea37db664fd08683b01decdadae636aaa1bfd7bb9669cbd8de": {
"Name": "box2",
"EndpointID": "4cf0f635d4273066acd3075ec775e6fa405034f94b88c1bcacdaae847612f2c5",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"com.docker.network.bridge.name": "docker0",
# brctl 顯示bridge tool
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242759468cf no veth8c9bb82
vethd8f9afb
容器對外通訊->iptables nat
$ ip route
default via 10.0.2.2 dev eth0 proto dhcp metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.200.0/24 dev eth1 proto kernel scope link src 192.168.200.10 metric 101
$ sudo iptables --list -t nat
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere !loopback/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 anywhere
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- anywhere anywhere
docker network create --help
docker network create -d {driver} --gateway {gateway} --subnet {subnet} {name}
docker network create -d bridge --gateway 127.200.0.1 -subnet 172.200.0.0/16 demo
[tomcat@docker-qc html-doc]$ docker network create -d bridge mybridge
65c5ba6cbea2fa677e8dd0623a510f3461e24ccac196b7374fc5e8d03803ec9e
[tomcat@docker-qc html-doc]$ docker network ls
NETWORK ID NAME DRIVER SCOPE
780162e5fe57 bridge bridge local
3c5b59c8a708 host host local
65c5ba6cbea2 mybridge bridge local
[tomcat@docker-qc html-doc]$ docker network inspect mybridge
[
{
"Name": "mybridge",
"Id": "65c5ba6cbea2fa677e8dd0623a510f3461e24ccac196b7374fc5e8d03803ec9e",
"Created": "2022-01-18T18:44:39.95740673+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
# 建立連接mybridge的container (--network 不設定會用預設的)
$ docker container run -d --rm --name box3 --network mybridge busybox /bin/sh -c "while true; do sleep 3600; done"
# 既有container 連接mybridge
$ docker network connect mybridge box1
# container 中斷 mybridge
$ docker network disconnect mybridge box1
Network type
- bridge
- host
- none
bridge: 新增一網段,橋接本地網路
host: 直接使用本地網路(無法看到port -> 等於直接開在本機port 80,好處不用nat,壞處只能開一個)
#docker dontainer run -d --name {container name} --network {network type} {image}
docker dontainer run -d --name web --network host nginx
只能開一個,多開會報錯
none: 只有 lookback 127.0.0.1 沒有其他網路
網路命名空間
-
bridge tool : brctl 安裝
#Ubuntu
apt-get install bridge-utils
#Centos
sudo yum install bridge-utils
創建bridge
[vagrant@docker-host1 ~]$ sudo brctl addbr mydocker0
[vagrant@docker-host1 ~]$ brctl show
bridge name bridge id STP enabled interfaces
mydocker0 8000.000000000000 no
[vagrant@docker-host1 ~]$
add-ns-to-br.sh
#!/bin/bash
bridge=$1
namespace=$2
addr=$3
vethA=veth-$namespace
vethB=eth00
sudo ip netns add $namespace
sudo ip link add $vethA type veth peer name $vethB
sudo ip link set $vethB netns $namespace
sudo ip netns exec $namespace ip addr add $addr dev $vethB
sudo ip netns exec $namespace ip link set $vethB up
sudo ip link set $vethA up
sudo brctl addif $bridge $vethA
#run shell
[vagrant@docker-host1 ~]$ sh add-ns-to-br.sh mydocker0 ns1 172.16.1.1/16
[vagrant@docker-host1 ~]$ sh add-ns-to-br.sh mydocker0 ns2 172.16.1.2/16
#up bridge
[vagrant@docker-host1 ~]$ sudo ip link set dev mydocker0 up
驗證
[vagrant@docker-host1 ~]$ sudo ip netns exec ns1 bash
[root@docker-host1 vagrant]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: eth00@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether f2:59:19:34:73:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.16.1.1/16 scope global eth00
valid_lft forever preferred_lft forever
inet6 fe80::f059:19ff:fe34:7370/64 scope link
valid_lft forever preferred_lft forever
[root@docker-host1 vagrant]# ping 172.16.1.2
PING 172.16.1.2 (172.16.1.2) 56(84) bytes of data.
64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=0.029 ms
64 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=0.080 ms
^C
--- 172.16.1.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.029/0.054/0.080/0.026 ms
[root@docker-host1 vagrant]#
使用iptables 實現nat
NAT with Linux and iptables - Tutorial (Introduction) (karlrupp.net)
使用自定義網路練習
使用自定義網路,預設自帶dns,容器間可相互使用name ping 通
準備python 檔案 app.py
from flask import Flask
from redis import Redis
import os
import socket
app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)
@app.route('/')
def hello():
redis.incr('hits')
return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"
準備一個 Dockerfile
FROM python:3.9.5-slim
RUN pip install flask redis && \
groupadd -r flask && useradd -r -g flask flask && \
mkdir /src && \
chown -R flask:flask /src
USER flask
COPY app.py /src/app.py
WORKDIR /src
ENV FLASK_APP=app.py REDIS_HOST=redis
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
build image 並下載一個redis image
$ docker image pull redis
$ docker image build -t flask-demo .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-demo latest 4778411a24c5 About a minute ago 126MB
python 3.9.5-slim c71955050276 8 days ago 115MB
redis latest 08502081bff6 2 weeks ago 105MB
建立一個bridge
$ docker network create -d bridge demo-network
8005f4348c44ffe3cdcbbda165beea2b0cb520179d3745b24e8f9e05a3e6456d
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
2a464c0b8ec7 bridge bridge local
8005f4348c44 demo-network bridge local
80b63f711a37 host host local
fae746a75be1 none null local
建立redis container 並連至 demo-network
$ docker container run -d --name redis-server --network demo-network redis
002800c265020310231d689e6fd35bc084a0fa015e8b0a3174aa2c5e29824c0e
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
002800c26502 redis "docker-entrypoint.s…" 4 seconds ago Up 3 seconds 6379/tcp redis-server
建立 flask container
$ docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo
打開瀏覽器 http://127.0.0.1:5000
可以看到以下內容,每次刷新次次就會+1
Hello Container World! I have been seen 36 times and my hostname is 925ecb8d111a.
整理後腳本
# prepare image
docker image pull redis
docker image build -t flask-demo .
# create network
docker network create -d bridge demo-network
# create container
docker container run -d --name redis-server --network demo-network redis
docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo
Docker Swarm
相關指令
# 查詢swarm狀態
docker info
# 未開啟
Swarm: inactive
# 初始化swarm
docker swarm init
##########################
Swarm initialized: current node (4szs3vf8x9pb79me4cxfsb9m2) is now a manager.
To add a worker to this swarm, run the following command:
#加入叢集使用以下命令
docker swarm join --token SWMTKN-1-3i2aq39bqqudp6p6y3o4ubw4ca1036z85z3huf9lyfijdfns88-33y43c8gw2ch85wbl2jdb4w0e 192.168.65.6:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
##########################
#查詢節點
docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
4szs3vf8x9pb79me4cxfsb9m2 * docker-desktop Ready Active Leader 20.10.6
# 開啟後查詢 docker info
Swarm: active
NodeID: 4szs3vf8x9pb79me4cxfsb9m2
Is Manager: true
ClusterID: 83phccx66ca4rji17wk8p5kce
Managers: 1
Nodes: 1
Default Address Pool: 10.0.0.0/8
SubnetSize: 24
Data Path Port: 4789
Orchestration:
# 離開docker swarm 叢集
docker swrm leave -f
docker swarm init 背後發生了什麼
主要是PKI和安全相關的自動化
-
創建swarm集群的根證書
-
manager節點的證書
-
其它節點加入集群需要的tokens
創建Raft資料庫用於存儲證書,配置,密碼等數據
建立一個service(container)
# docker service create {image}:{tag}
docker service create --name web nginx:latest
# 取得servie id(不是container id)
docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
l5wvuxzw9l96 jolly_euclid replicated 1/1 nginx:latest
# 查看container
docker service ps l5wvuxzw9l96
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
vp8dkrsqslxw jolly_euclid.1 nginx:latest docker-desktop Running Running 3 minutes ago
# 建立3個副本(update)
docker service update l5wvuxzw9l96 --replicas 3
l5wvuxzw9l96
overall progress: 3 out of 3 tasks
1/3: running [==================================================>]
2/3: running [==================================================>]
3/3: running [==================================================>]
# 建立4個副本(scale)
docker service scale l5wvuxzw9l96=4
web scaled to 4
overall progress: 4 out of 4 tasks
1/4: running [==================================================>]
2/4: running [==================================================>]
3/4: running [==================================================>]
4/4: running [==================================================>]
#docker sercie ps
docker service ps l5wvuxzw9l96
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
vp8dkrsqslxw jolly_euclid.1 nginx:latest docker-desktop Running Running 13 minutes ago
qavp0npxko3l jolly_euclid.2 nginx:latest docker-desktop Running Running 7 minutes ago
kqs6uvbkxmjh jolly_euclid.3 nginx:latest docker-desktop Running Running 7 minutes ago
service 刪除
docker service rm l5wvuxzw9l96
docker swarm的網路:
-
第一是外部如何訪問部署運行在swarm集群內的服務,可以稱之為
入方向流量,在swarm裡我們通過ingress來解決 -
第二是部署在swarm集群裡的服務,如何對外進行訪問,這部分又分為兩塊:
-
第一,
東西向流量,也就是不同swarm節點上的容器之間如何通信,swarm通過overlay網絡來解決; -
第二,
南北向流量,也就是swarm集群裡的容器如何對外訪問,比如互聯網,這個是Linux bridge + iptables NAT來解決的
-
Docker如何固定IP設定的方法
Docker如何固定IP設定的方法 - IT閱讀 (itread01.com)
經常用Docker模擬專案在生產環境中的部署,往往需要同時開好幾臺Docker容器,而且有時安裝的軟體需要繫結Docker區域網中的其他容器,如 MongoDB 副本集部署的時候,就需要繫結其他容器的內網IP。
但是,Docker 每次重啟後,容器的IP地址會變化,查詢了資料,Docker是支援設定固定IP的。
Docker 預設網路
Docker安裝後,預設會建立下面三種網路型別:
$ docker network ls NETWORK ID NAME DRIVER SCOPE 9781b1f585ae bridge bridge local 1252da701e55 host host local 237ea3d5cfbf none null local
啟動 Docker的時候,用 --network 引數,可以指定網路型別,如:
~ docker run -itd --name test1 --network bridge --ip 172.17.0.10 centos:latest /bin/bash
bridge:橋接網路
預設情況下啟動的Docker容器,都是使用 bridge,Docker安裝時建立的橋接網路,每次Docker容器重啟時,會按照順序獲取對應的IP地址,這個就導致重啟下,Docker的IP地址就變了
none:無指定網路
使用 --network=none ,docker 容器就不會分配區域網的IP
host: 主機網路
使用 --network=host,此時,Docker 容器的網路會附屬在主機上,兩者是互通的。
例如,在容器中執行一個Web服務,監聽8080埠,則主機的8080埠就會自動對映到容器中。
建立自定義網路:(設定固定IP)
啟動Docker容器的時候,使用預設的網路是不支援指派固定IP的,如下:
~ docker run -itd --net bridge --ip 172.17.0.10 centos:latest /bin/bash 6eb1f228cf308d1c60db30093c126acbfd0cb21d76cb448c678bab0f1a7c0df6 docker: Error response from daemon: User specified IP address is supported on user defined networks only.
因此,需要建立自定義網路,下面是具體的步驟:
步驟1: 建立自定義網路
建立自定義網路,並且指定網段:172.18.0.0/16
➜ ~ docker network create --subnet=172.18.0.0/16 mynetwork ➜ ~ docker network ls NETWORK ID NAME DRIVER SCOPE 9781b1f585ae bridge bridge local 1252da701e55 host host local 4f11ae9c85de mynetwork bridge local 237ea3d5cfbf none null local
步驟2: 建立Docker容器
程式碼如下:
➜ ~ docker run -itd --name networkTest1 --net mynetwork --ip 172.18.0.2 centos:latest /bin/bash
這個時候,建立的Docker容器就會持有 172.18.0.2 這個IP.
[root@ec8e31938fe7 /]# ifconfig eth0 Link encap:Ethernet HWaddr 02:42:AC:12:00:02 inet addr:172.18.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::42:acff:fe12:2/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:88 errors:0 dropped:0 overruns:0 frame:0 TX packets:14 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:4056 (3.9 KiB) TX bytes:1068 (1.0 KiB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:0 (0.0 b) TX bytes:0 (0.0 b)
Docker安裝
Centos
Docker Engine on CentOS | Docker Documentation
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
sudo yum install -y yum-utils
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
sudo yum-config-manager --enable docker-ce-nightly
sudo yum install docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
sudo systemctl enable docker
~/.docker/config.json
{
"proxies":
{
"default":
{
"httpProxy": "http://192.168.1.12:3128",
"httpsProxy": "http://192.168.1.12:3128",
"noProxy": "*.test.example.com,.example2.com,127.0.0.0/8"
}
}
}
/etc/docker/daemon.json
{
"registry-mirrors":["http://hub-mirror.c.163.com"],
}
registry-1.docker.io. 54 IN A 34.197.211.151
registry-1.docker.io. 54 IN A 34.192.145.113
registry-1.docker.io. 54 IN A 18.214.230.110
registry-1.docker.io. 54 IN A 52.55.168.20
registry-1.docker.io. 54 IN A 3.229.227.53
registry-1.docker.io. 54 IN A 3.209.182.229
registry-1.docker.io. 54 IN A 3.213.204.48
registry-1.docker.io. 54 IN A 54.161.109.204
mkdir /etc/systemd/system/docker.service.d
vim /etc/systemd/system/docker.service.d/http-proxy.conf
########################################################
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:80/"
Environment="NO_PROXY=localhost,127.0.0.0/8,docker-registry.somecorporation.com"
########################################################
sudo systemctl daemon-reload
sudo systemctl show --property Environment docker
sudo systemctl restart docker
使用get.docker.com 腳本
curl -L get.docker.com -o get-docker.sh
sh get-docker.sh
#!/bin/sh
set -e
# Docker CE for Linux installation script
#
# See https://docs.docker.com/engine/install/ for the installation steps.
#
# This script is meant for quick & easy install via:
# $ curl -fsSL https://get.docker.com -o get-docker.sh
# $ sh get-docker.sh
#
# For test builds (ie. release candidates):
# $ curl -fsSL https://test.docker.com -o test-docker.sh
# $ sh test-docker.sh
#
# NOTE: Make sure to verify the contents of the script
# you downloaded matches the contents of install.sh
# located at https://github.com/docker/docker-install
# before executing.
#
# Git commit from https://github.com/docker/docker-install when
# the script was uploaded (Should only be modified by upload job):
SCRIPT_COMMIT_SHA="93d2499759296ac1f9c510605fef85052a2c32be"
# strip "v" prefix if present
VERSION="${VERSION#v}"
# The channel to install from:
# * nightly
# * test
# * stable
# * edge (deprecated)
DEFAULT_CHANNEL_VALUE="stable"
if [ -z "$CHANNEL" ]; then
CHANNEL=$DEFAULT_CHANNEL_VALUE
fi
DEFAULT_DOWNLOAD_URL="https://download.docker.com"
if [ -z "$DOWNLOAD_URL" ]; then
DOWNLOAD_URL=$DEFAULT_DOWNLOAD_URL
fi
DEFAULT_REPO_FILE="docker-ce.repo"
if [ -z "$REPO_FILE" ]; then
REPO_FILE="$DEFAULT_REPO_FILE"
fi
mirror=''
DRY_RUN=${DRY_RUN:-}
while [ $# -gt 0 ]; do
case "$1" in
--mirror)
mirror="$2"
shift
;;
--dry-run)
DRY_RUN=1
;;
--*)
echo "Illegal option $1"
;;
esac
shift $(( $# > 0 ? 1 : 0 ))
done
case "$mirror" in
Aliyun)
DOWNLOAD_URL="https://mirrors.aliyun.com/docker-ce"
;;
AzureChinaCloud)
DOWNLOAD_URL="https://mirror.azure.cn/docker-ce"
;;
esac
command_exists() {
command -v "$@" > /dev/null 2>&1
}
# version_gte checks if the version specified in $VERSION is at least
# the given CalVer (YY.MM) version. returns 0 (success) if $VERSION is either
# unset (=latest) or newer or equal than the specified version. Returns 1 (fail)
# otherwise.
#
# examples:
#
# VERSION=20.10
# version_gte 20.10 // 0 (success)
# version_gte 19.03 // 0 (success)
# version_gte 21.10 // 1 (fail)
version_gte() {
if [ -z "$VERSION" ]; then
return 0
fi
eval calver_compare "$VERSION" "$1"
}
# calver_compare compares two CalVer (YY.MM) version strings. returns 0 (success)
# if version A is newer or equal than version B, or 1 (fail) otherwise. Patch
# releases and pre-release (-alpha/-beta) are not taken into account
#
# examples:
#
# calver_compare 20.10 19.03 // 0 (success)
# calver_compare 20.10 20.10 // 0 (success)
# calver_compare 19.03 20.10 // 1 (fail)
calver_compare() (
set +x
yy_a="$(echo "$1" | cut -d'.' -f1)"
yy_b="$(echo "$2" | cut -d'.' -f1)"
if [ "$yy_a" -lt "$yy_b" ]; then
return 1
fi
if [ "$yy_a" -gt "$yy_b" ]; then
return 0
fi
mm_a="$(echo "$1" | cut -d'.' -f2)"
mm_b="$(echo "$2" | cut -d'.' -f2)"
if [ "${mm_a#0}" -lt "${mm_b#0}" ]; then
return 1
fi
return 0
)
is_dry_run() {
if [ -z "$DRY_RUN" ]; then
return 1
else
return 0
fi
}
is_wsl() {
case "$(uname -r)" in
*microsoft* ) true ;; # WSL 2
*Microsoft* ) true ;; # WSL 1
* ) false;;
esac
}
is_darwin() {
case "$(uname -s)" in
*darwin* ) true ;;
*Darwin* ) true ;;
* ) false;;
esac
}
deprecation_notice() {
distro=$1
distro_version=$2
echo
printf "\033[91;1mDEPRECATION WARNING\033[0m\n"
printf " This Linux distribution (\033[1m%s %s\033[0m) reached end-of-life and is no longer supported by this script.\n" "$distro" "$distro_version"
echo " No updates or security fixes will be released for this distribution, and users are recommended"
echo " to upgrade to a currently maintained version of $distro."
echo
printf "Press \033[1mCtrl+C\033[0m now to abort this script, or wait for the installation to continue."
echo
sleep 10
}
get_distribution() {
lsb_dist=""
# Every system that we officially support has /etc/os-release
if [ -r /etc/os-release ]; then
lsb_dist="$(. /etc/os-release && echo "$ID")"
fi
# Returning an empty string here should be alright since the
# case statements don't act unless you provide an actual value
echo "$lsb_dist"
}
echo_docker_as_nonroot() {
if is_dry_run; then
return
fi
if command_exists docker && [ -e /var/run/docker.sock ]; then
(
set -x
$sh_c 'docker version'
) || true
fi
# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output
echo
echo "================================================================================"
echo
if version_gte "20.10"; then
echo "To run Docker as a non-privileged user, consider setting up the"
echo "Docker daemon in rootless mode for your user:"
echo
echo " dockerd-rootless-setuptool.sh install"
echo
echo "Visit https://docs.docker.com/go/rootless/ to learn about rootless mode."
echo
fi
echo
echo "To run the Docker daemon as a fully privileged service, but granting non-root"
echo "users access, refer to https://docs.docker.com/go/daemon-access/"
echo
echo "WARNING: Access to the remote API on a privileged Docker daemon is equivalent"
echo " to root access on the host. Refer to the 'Docker daemon attack surface'"
echo " documentation for details: https://docs.docker.com/go/attack-surface/"
echo
echo "================================================================================"
echo
}
# Check if this is a forked Linux distro
check_forked() {
# Check for lsb_release command existence, it usually exists in forked distros
if command_exists lsb_release; then
# Check if the `-u` option is supported
set +e
lsb_release -a -u > /dev/null 2>&1
lsb_release_exit_code=$?
set -e
# Check if the command has exited successfully, it means we're in a forked distro
if [ "$lsb_release_exit_code" = "0" ]; then
# Print info about current distro
cat <<-EOF
You're using '$lsb_dist' version '$dist_version'.
EOF
# Get the upstream release info
lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[:space:]')
dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[:space:]')
# Print info about upstream distro
cat <<-EOF
Upstream release is '$lsb_dist' version '$dist_version'.
EOF
else
if [ -r /etc/debian_version ] && [ "$lsb_dist" != "ubuntu" ] && [ "$lsb_dist" != "raspbian" ]; then
if [ "$lsb_dist" = "osmc" ]; then
# OSMC runs Raspbian
lsb_dist=raspbian
else
# We're Debian and don't even know it!
lsb_dist=debian
fi
dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
case "$dist_version" in
11)
dist_version="bullseye"
;;
10)
dist_version="buster"
;;
9)
dist_version="stretch"
;;
8)
dist_version="jessie"
;;
esac
fi
fi
fi
}
do_install() {
echo "# Executing docker install script, commit: $SCRIPT_COMMIT_SHA"
if command_exists docker; then
cat >&2 <<-'EOF'
Warning: the "docker" command appears to already exist on this system.
If you already have Docker installed, this script can cause trouble, which is
why we're displaying this warning and provide the opportunity to cancel the
installation.
If you installed the current Docker package using this script and are using it
again to update Docker, you can safely ignore this message.
You may press Ctrl+C now to abort this script.
EOF
( set -x; sleep 20 )
fi
user="$(id -un 2>/dev/null || true)"
sh_c='sh -c'
if [ "$user" != 'root' ]; then
if command_exists sudo; then
sh_c='sudo -E sh -c'
elif command_exists su; then
sh_c='su -c'
else
cat >&2 <<-'EOF'
Error: this installer needs the ability to run commands as root.
We are unable to find either "sudo" or "su" available to make this happen.
EOF
exit 1
fi
fi
if is_dry_run; then
sh_c="echo"
fi
# perform some very rudimentary platform detection
lsb_dist=$( get_distribution )
lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"
if is_wsl; then
echo
echo "WSL DETECTED: We recommend using Docker Desktop for Windows."
echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop"
echo
cat >&2 <<-'EOF'
You may press Ctrl+C now to abort this script.
EOF
( set -x; sleep 20 )
fi
case "$lsb_dist" in
ubuntu)
if command_exists lsb_release; then
dist_version="$(lsb_release --codename | cut -f2)"
fi
if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then
dist_version="$(. /etc/lsb-release && echo "$DISTRIB_CODENAME")"
fi
;;
debian|raspbian)
dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
case "$dist_version" in
11)
dist_version="bullseye"
;;
10)
dist_version="buster"
;;
9)
dist_version="stretch"
;;
8)
dist_version="jessie"
;;
esac
;;
centos|rhel|sles)
if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
fi
;;
*)
if command_exists lsb_release; then
dist_version="$(lsb_release --release | cut -f2)"
fi
if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
fi
;;
esac
# Check if this is a forked Linux distro
check_forked
# Print deprecation warnings for distro versions that recently reached EOL,
# but may still be commonly used (especially LTS versions).
case "$lsb_dist.$dist_version" in
debian.stretch|debian.jessie)
deprecation_notice "$lsb_dist" "$dist_version"
;;
raspbian.stretch|raspbian.jessie)
deprecation_notice "$lsb_dist" "$dist_version"
;;
ubuntu.xenial|ubuntu.trusty)
deprecation_notice "$lsb_dist" "$dist_version"
;;
fedora.*)
if [ "$dist_version" -lt 33 ]; then
deprecation_notice "$lsb_dist" "$dist_version"
fi
;;
esac
# Run setup for each distro accordingly
case "$lsb_dist" in
ubuntu|debian|raspbian)
pre_reqs="apt-transport-https ca-certificates curl"
if ! command -v gpg > /dev/null; then
pre_reqs="$pre_reqs gnupg"
fi
apt_repo="deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] $DOWNLOAD_URL/linux/$lsb_dist $dist_version $CHANNEL"
(
if ! is_dry_run; then
set -x
fi
$sh_c 'apt-get update -qq >/dev/null'
$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq $pre_reqs >/dev/null"
$sh_c "curl -fsSL \"$DOWNLOAD_URL/linux/$lsb_dist/gpg\" | gpg --dearmor --yes -o /usr/share/keyrings/docker-archive-keyring.gpg"
$sh_c "echo \"$apt_repo\" > /etc/apt/sources.list.d/docker.list"
$sh_c 'apt-get update -qq >/dev/null'
)
pkg_version=""
if [ -n "$VERSION" ]; then
if is_dry_run; then
echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
else
# Will work for incomplete versions IE (17.12), but may not actually grab the "latest" if in the test channel
pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/~ce~.*/g" | sed "s/-/.*/g").*-0~$lsb_dist"
search_command="apt-cache madison 'docker-ce' | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
pkg_version="$($sh_c "$search_command")"
echo "INFO: Searching repository for VERSION '$VERSION'"
echo "INFO: $search_command"
if [ -z "$pkg_version" ]; then
echo
echo "ERROR: '$VERSION' not found amongst apt-cache madison results"
echo
exit 1
fi
if version_gte "18.09"; then
search_command="apt-cache madison 'docker-ce-cli' | grep '$pkg_pattern' | head -1 | awk '{\$1=\$1};1' | cut -d' ' -f 3"
echo "INFO: $search_command"
cli_pkg_version="=$($sh_c "$search_command")"
fi
pkg_version="=$pkg_version"
fi
fi
(
pkgs=""
if version_gte "18.09"; then
# older versions don't support a cli package
pkgs="$pkgs docker-ce-cli${cli_pkg_version%=}"
fi
if version_gte "20.10" && [ "$(uname -m)" = "x86_64" ]; then
# also install the latest version of the "docker scan" cli-plugin (only supported on x86 currently)
pkgs="$pkgs docker-scan-plugin"
fi
pkgs="$pkgs docker-ce${pkg_version%=}"
if ! is_dry_run; then
set -x
fi
$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends $pkgs >/dev/null"
if version_gte "20.10"; then
# Install docker-ce-rootless-extras without "--no-install-recommends", so as to install slirp4netns when available
$sh_c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-ce-rootless-extras${pkg_version%=} >/dev/null"
fi
)
echo_docker_as_nonroot
exit 0
;;
centos|fedora|rhel)
if [ "$(uname -m)" != "s390x" ] && [ "$lsb_dist" = "rhel" ]; then
echo "Packages for RHEL are currently only available for s390x."
exit 1
fi
yum_repo="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE"
if ! curl -Ifs "$yum_repo" > /dev/null; then
echo "Error: Unable to curl repository file $yum_repo, is it valid?"
exit 1
fi
if [ "$lsb_dist" = "fedora" ]; then
pkg_manager="dnf"
config_manager="dnf config-manager"
enable_channel_flag="--set-enabled"
disable_channel_flag="--set-disabled"
pre_reqs="dnf-plugins-core"
pkg_suffix="fc$dist_version"
else
pkg_manager="yum"
config_manager="yum-config-manager"
enable_channel_flag="--enable"
disable_channel_flag="--disable"
pre_reqs="yum-utils"
pkg_suffix="el"
fi
(
if ! is_dry_run; then
set -x
fi
$sh_c "$pkg_manager install -y -q $pre_reqs"
$sh_c "$config_manager --add-repo $yum_repo"
if [ "$CHANNEL" != "stable" ]; then
$sh_c "$config_manager $disable_channel_flag docker-ce-*"
$sh_c "$config_manager $enable_channel_flag docker-ce-$CHANNEL"
fi
$sh_c "$pkg_manager makecache"
)
pkg_version=""
if [ -n "$VERSION" ]; then
if is_dry_run; then
echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
else
pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/\\\\.ce.*/g" | sed "s/-/.*/g").*$pkg_suffix"
search_command="$pkg_manager list --showduplicates 'docker-ce' | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
pkg_version="$($sh_c "$search_command")"
echo "INFO: Searching repository for VERSION '$VERSION'"
echo "INFO: $search_command"
if [ -z "$pkg_version" ]; then
echo
echo "ERROR: '$VERSION' not found amongst $pkg_manager list results"
echo
exit 1
fi
if version_gte "18.09"; then
# older versions don't support a cli package
search_command="$pkg_manager list --showduplicates 'docker-ce-cli' | grep '$pkg_pattern' | tail -1 | awk '{print \$2}'"
cli_pkg_version="$($sh_c "$search_command" | cut -d':' -f 2)"
fi
# Cut out the epoch and prefix with a '-'
pkg_version="-$(echo "$pkg_version" | cut -d':' -f 2)"
fi
fi
(
if ! is_dry_run; then
set -x
fi
# install the correct cli version first
if [ -n "$cli_pkg_version" ]; then
$sh_c "$pkg_manager install -y -q docker-ce-cli-$cli_pkg_version"
fi
$sh_c "$pkg_manager install -y -q docker-ce$pkg_version"
if version_gte "20.10"; then
$sh_c "$pkg_manager install -y -q docker-ce-rootless-extras$pkg_version"
fi
)
echo_docker_as_nonroot
exit 0
;;
sles)
if [ "$(uname -m)" != "s390x" ]; then
echo "Packages for SLES are currently only available for s390x"
exit 1
fi
sles_version="${dist_version##*.}"
sles_repo="$DOWNLOAD_URL/linux/$lsb_dist/$REPO_FILE"
opensuse_repo="https://download.opensuse.org/repositories/security:SELinux/SLE_15_SP$sles_version/security:SELinux.repo"
if ! curl -Ifs "$sles_repo" > /dev/null; then
echo "Error: Unable to curl repository file $sles_repo, is it valid?"
exit 1
fi
pre_reqs="ca-certificates curl libseccomp2 awk"
(
if ! is_dry_run; then
set -x
fi
$sh_c "zypper install -y $pre_reqs"
$sh_c "zypper addrepo $sles_repo"
if ! is_dry_run; then
cat >&2 <<-'EOF'
WARNING!!
openSUSE repository (https://download.opensuse.org/repositories/security:SELinux) will be enabled now.
Do you wish to continue?
You may press Ctrl+C now to abort this script.
EOF
( set -x; sleep 30 )
fi
$sh_c "zypper addrepo $opensuse_repo"
$sh_c "zypper --gpg-auto-import-keys refresh"
$sh_c "zypper lr -d"
)
pkg_version=""
if [ -n "$VERSION" ]; then
if is_dry_run; then
echo "# WARNING: VERSION pinning is not supported in DRY_RUN"
else
pkg_pattern="$(echo "$VERSION" | sed "s/-ce-/\\\\.ce.*/g" | sed "s/-/.*/g")"
search_command="zypper search -s --match-exact 'docker-ce' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'"
pkg_version="$($sh_c "$search_command")"
echo "INFO: Searching repository for VERSION '$VERSION'"
echo "INFO: $search_command"
if [ -z "$pkg_version" ]; then
echo
echo "ERROR: '$VERSION' not found amongst zypper list results"
echo
exit 1
fi
search_command="zypper search -s --match-exact 'docker-ce-cli' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'"
# It's okay for cli_pkg_version to be blank, since older versions don't support a cli package
cli_pkg_version="$($sh_c "$search_command")"
pkg_version="-$pkg_version"
search_command="zypper search -s --match-exact 'docker-ce-rootless-extras' | grep '$pkg_pattern' | tail -1 | awk '{print \$6}'"
rootless_pkg_version="$($sh_c "$search_command")"
rootless_pkg_version="-$rootless_pkg_version"
fi
fi
(
if ! is_dry_run; then
set -x
fi
# install the correct cli version first
if [ -n "$cli_pkg_version" ]; then
$sh_c "zypper install -y docker-ce-cli-$cli_pkg_version"
fi
$sh_c "zypper install -y docker-ce$pkg_version"
if version_gte "20.10"; then
$sh_c "zypper install -y docker-ce-rootless-extras$rootless_pkg_version"
fi
)
echo_docker_as_nonroot
exit 0
;;
*)
if [ -z "$lsb_dist" ]; then
if is_darwin; then
echo
echo "ERROR: Unsupported operating system 'macOS'"
echo "Please get Docker Desktop from https://www.docker.com/products/docker-desktop"
echo
exit 1
fi
fi
echo
echo "ERROR: Unsupported distribution '$lsb_dist'"
echo
exit 1
;;
esac
exit 1
}
# wrapped up in a function so that we have some protection against only getting
# half the file during "curl | sh"
do_install
Docker架構
Docker常用指令
*container 運行
#run:
#-d 背景執行
#nginx 執行什麼服務
#ng 自訂的tagname
#-name 自訂名稱
# -e 設定環境變數, TZ=Asia/Taipei => 設定時區
docker container run -d nginx
docker container run -d -name ngx nginx
docker container run -d nginx:ng
docker container run -d -e "TZ=Asia/Taipei" nginx
*查看運行container
docker container ls -a
[tomcat@docker-qc ~]$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
df6ab3df4b14 nginx "nginx -g 'daemon of…" 9 seconds ago Up 8 seconds 80/tcp modest_cray
940d3d804bcd nginx "nginx -g 'daemon of…" 7 minutes ago Up 7 minutes 80/tcp elastic_raman
*運行container 並進入shell
docker container run -it {container name} sh
*進入已運行container
# 進入名為apache的容器執行sh
docker exec -it apache sh
*複製檔案
# 建立一個hello.html
echo "hello" > hello.html
# 建立apache
docker run -d --name apache httpd:alpine
# 複製檔案至apache容器中
docker cp hello.html apache:/usr/local/apache2/htdocs/
# 複製容器中的檔案至本機
docker cp apache:/usr/local/apache2/htdocs/index.html ./index.html
*掛載目錄
# 把hello.html掛成index.html
docker run -d --name apache2 -v $PWD/hello.html:/usr/local/apache2/htdocs/index.html httpd:alpine
*建立外部連線
# -p port 對應 {本機}:{容器}
docker run -d -v $PWD.hell.html:/url/local/apache2/htdocs/index.html -p 80:80 httpd:alpine
*刪除container
#-f 強制
docker rm -f {container id}
#刪除所有容器
docker rm -f $(docker ps -a -q)
#刪除所有名字有demo的容器
docker rm -f $(docker ps -a -q -f name=demo)
# 停用所有container
docker stop $(docker ps -a -q)
# 刪除沒已使用的container 與 image
docker system prune
# 刪除所有映像檔
docker rmi $(docker images -a -q)
# 刪除所有name = <none> 的映像檔
docker rmi $(docker images -f "dangling=true" -q)
# 刪除所有名稱叫shopcar的映像檔
docker rmi $(docker images shopcar* -q)
# 清除volume
docker volume rm $(docker volume ls)
# 刪除所有沒有使用的container image volume
docker system prune -a -f --volumes
#起一個不會停止的centos
docker run -d --name centos7 centos:7 sleep infinity
查看要效能
*從主機查看container process
docker container top {container id}
*查看狀態
# 容器狀態
docker stats {container id | name }
# cpu ram
docker top {container id | name }
# log
docker logs {container id | name }
docker logs -f {container id | name }
Dockerfile 的 /etc/hosts 如何設定
來源:
Dockerfile 的 /etc/hosts 如何設定 – Tsung's Blog (longwin.com.tw)
Dockerfile 的 /etc/hosts 如何設定
於 Dockerfile reference 文件裡面,沒有看到 /etc/hosts 該怎麼改,後來發現 /etc/hosts 修改的方式還蠻黑暗的~
修改 /etc/hosts 的作法有兩種,先講比較正確的方法:
Docker 增加 hosts 可以參考 Docker run 的文件:Docker run reference
於 Docker run 使用 --add-host=server-name:127.0.0.1,多個用空白隔開,繼續寫下去即可,範例如下:
- docker run ... --add-host='server:0.0.0.0' ...
- docker run ... --add-host='server:127.0.0.1' --add-host='server2:127.0.0.2'...
若有 /etc/hosts 有太多需要增加,可以使用下述 Shell script 產生 --add-host 參數
- /etc/hosts 若要 1 vs 1 的 ip host 對應
- cat /etc/hosts | sed 's/^#.*//g' | grep '[0-9][0-9]' | tr "\t" " " | awk '{print "--add-host="$2":"$1 }' | tr '\n' ' '
- /etc/hosts 若要 1 vs n(多) 的 ip host 對應
- cat /etc/hosts | grep -v "^#" | awk '{for(i=2;i<=NF;i++){print "--add-host "$i":"$1}}'
再來看比較暴力的作法:(直接寫在 Dockerfile 裡面)
# 使用 /tmp/hosts 當 /etc/hosts COPY /etc/hosts /tmp/hosts.bak
RUN cp hosts /tmp/hosts # hosts 於外部 cp /etc/hosts .
#RUN cp /etc/hosts /tmp/hosts #路径长度最好保持一致
RUN mkdir -p -- /lib-override && cp /lib/x86_64-linux-gnu/libnss_files.so.2 /lib-override
RUN sed -i 's:/etc/hosts:/tmp/hosts:g' /lib-override/libnss_files.so.2
ENV LD_LIBRARY_PATH /lib-override
RUN cat /tmp/hosts.bak >> /tmp/hosts
Docker 內部讀取 /etc/hosts 的部份,修改成讀取 /tmp/hosts (參考自此篇:Docker 修改 hosts)
將下述寫入 Dockerfile,於 build 的時候,就會讀取 /tmp/hosts 來操作囉~
相關網頁
Docker 使用私有repo (Nexus)
vim /etc/docker/daemon.json
{
"insecure-registries": ["10.10.10.10:8083"],
"registry-mirrors": ["http://10.10.10.10:8083"]
}
sudo systemctl restart docker
https://registry-1.docker.io
Docker 內 container 如何使用Docker
在 Docker 容器內部直接執行宿主機上的 Docker 命令有一定的限制,因為 Docker 容器本身是與宿主機隔離的。以下是一些實現方法,根據不同需求選擇適合的解法:
方法 1:將 Docker socket 映射到容器
將宿主機的 Docker socket (/var/run/docker.sock) 映射到容器內部,這樣容器內部的 Docker CLI 可以直接與宿主機的 Docker daemon 通訊。
步驟
-
啟動容器時掛載 Docker socket:
docker run -it --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(which docker):/usr/bin/docker \ your-image -
在容器內使用 Docker 命令: 容器內的 Docker CLI 可以直接操作宿主機的 Docker,執行例如
docker ps、docker run等指令。 -
注意安全性: 授予容器訪問 Docker socket 的權限會帶來安全風險,因為容器內的進程可以完全控制宿主機的 Docker。僅在信任的環境下使用此方法。
方法 2:使用 Docker-in-Docker (DinD)
如果希望在容器內運行獨立的 Docker daemon,可以使用 Docker 官方的 docker:dind 映像。
步驟
-
啟動 DinD 容器:
docker run --privileged --name dind-container -d docker:dind -
進入容器:
docker exec -it dind-container sh -
在容器內執行 Docker 命令(這是獨立的 Docker daemon,不會影響宿主機的 Docker)。
方法 3:使用 Docker CLI 並通過 API 操作
宿主機的 Docker daemon 提供 REST API,可以在容器內安裝 Docker CLI 並通過 API 操作。
步驟
-
確保宿主機的 Docker daemon 開啟遠程 API,編輯
/etc/docker/daemon.json:{ "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"] }然後重啟 Docker:
sudo systemctl restart docker -
啟動容器,並配置環境變數
DOCKER_HOST指向宿主機 API:docker run -it --rm \ -e DOCKER_HOST=tcp://<host-ip>:2375 \ your-image -
在容器內執行 Docker 命令:
docker ps
注意事項
-
安全風險:
- 授予容器訪問 Docker socket 或 Docker daemon 的權限可能導致安全漏洞。
- 確保只在受信任的環境中使用,並採取必要的隔離措施(例如:限制網路訪問、使用 TLS 保護 API)。
-
性能影響:
- 如果容器內需要頻繁操作宿主機的 Docker,可能會影響宿主機的性能。
-
權限問題:
- 確保容器內的進程有足夠的權限訪問
/var/run/docker.sock或 Docker API。
- 確保容器內的進程有足夠的權限訪問
希望這些方法能幫助你解決問題!如果需要更詳細的操作指引,可以告訴我具體的需求或環境設定。
【Docker】上傳自己的 Docker image 到 Docker Hub
要上傳自己的 Docker image 到 Docker Hub,請按照以下步驟操作:
1. 登入 Docker Hub
確保你的電腦已經安裝了 Docker,然後使用以下命令登入 Docker Hub:
docker login
系統會要求輸入你的 Docker Hub 帳號和密碼。
2. 建立 Docker Image
如果你還沒有 Docker image,可以使用 docker build 指令來建立:
docker build -t <你的DockerHub帳號>/<image名稱>:<tag> .
例如:
docker build -t myusername/myapp:v1 .
這樣就會建立一個 myusername/myapp 的 image,標籤(tag)為 v1。
3. 標記 (Tag) Image
如果你已經有一個本地的 image,但還沒有加上 Docker Hub 的 repository 名稱,你需要先標記它:
docker tag <本地image ID或名稱> <你的DockerHub帳號>/<image名稱>:<tag>
例如:
docker tag myapp:latest myusername/myapp:v1
4. 推送 (Push) Image
將 image 推送到 Docker Hub:
docker push <你的DockerHub帳號>/<image名稱>:<tag>
例如:
docker push myusername/myapp:v1
5. 確認上傳成功
你可以到 Docker Hub 查看你的 repository,或者在其他機器上使用以下命令來拉取(pull)該 image:
docker pull myusername/myapp:v1
常見問題
-
登入失敗:
- 如果
docker login失敗,可以先在 Docker Hub 上確認你的帳號密碼是否正確。 - 如果你使用的是 Docker Hub 產生的 Access Token,請在密碼欄位輸入該 Token。
- 如果
-
推送失敗,沒有權限 (denied: requested access to the resource is denied):
- 確保你的 image tag 是
你的DockerHub帳號/你的image名稱,而不是本地的名稱。 - 使用
docker login確保你已登入正確的帳號。
- 確保你的 image tag 是
這樣你就能成功上傳自己的 Docker image 到 Docker Hub 了! 🚀
【Docker】自動分配網路用盡
failed to create network pub-sub_pubsub: Error response from daemon: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network
👉 這個錯誤的意思是:
Docker 沒有可用的 IP 網段 來建立新的 network。
Docker 每次起新的 network,會自動從預設的 IP range (例如 172.18.0.0/16) 分配一段子網(subnet)來給你的 docker-compose。
但因為你本機已經有太多 Docker network(或之前的 container network 沒清乾淨),導致 IP address range 已經用光了。
解法,詳細往下看
# 查詢目前所有container網段
docker network inspect $(docker network ls -q) --format '{{.Name}}: {{(index .IPAM.Config 0).Subnet}}'
# 刪除無用network
docker network prune
#解法,指定網段
networks:
ngtest:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/24
🛠️ 解法 1:刪掉無用的 Docker Network
先看一下目前的 network:
docker network ls
會看到一堆像這樣:
| NETWORK ID | NAME | DRIVER | SCOPE |
|---|---|---|---|
| 1a2b3c4d5e | bridge | bridge | local |
| 6f7g8h9i0j | pub-sub_pubsub | bridge | local |
| ... | ... | ... | ... |
然後手動刪掉沒用的 network,例如:
docker network rm network_name
例如:
docker network rm pub-sub_pubsub
要一口氣刪很多:
docker network prune
(⚡小心!prune 會把所有未使用的網路刪掉。)
🛠️ 解法 2:docker-compose.yml 手動指定 IP 範圍
如果你想避免這個問題,以後可以自己在 docker-compose.yml 裡面設定固定的子網段,例如:
networks:
pubsub-net:
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/16
然後每個 service 指定 network:
services:
pubsub-emulator:
image: google/cloud-sdk:latest
networks:
- pubsub-net
這樣 Docker 就不會自己亂分配 IP,會用你指定的 172.30.0.0/16,可以大大降低出錯的機會。
🔥 最快速推薦操作
直接下這兩行,清除掉無用網路 ➔ 再重開 docker-compose:
docker network prune -f
docker-compose up
🛎️ 小提醒
| 問題情境 | 說明 |
|---|---|
| 通常出現在 Docker Desktop 很久沒清 | 每次起 network,Docker 自動分配,結果越堆越多 |
docker network ls 看太多 bridge |
可以確認哪些是 zombie network |
docker-compose down 不一定會清 network |
要自己下 prune 或 network rm |
好問題!
你問的這個其實就是:
如何查目前 Docker 所有 network 的網段(subnet),確保自己新建的網段不會衝突?
這個是專業級的 docker networking 操作了,我來直接教你最正確、完整又快速的方法 👨🏻💻
✅ 查詢目前所有 Docker network 網段的方法
直接打:
docker network inspect $(docker network ls -q) --format '{{.Name}}: {{(index .IPAM.Config 0).Subnet}}'
這條指令的意思是:
-
docker network ls -q:列出所有 network 的 ID -
docker network inspect:逐個去查 network 詳細資訊 -
--format:只輸出你要看的部分(名字 + 子網範圍)
👉 出來結果像這樣:
bridge: 172.17.0.0/16
host:
none:
myproject_default: 172.21.0.0/16
another-network: 172.22.0.0/16
如果某些 network 是空的(像 host、none),那是正常的,因為它們沒有自己管理 IP。
🛠️ 查完之後怎麼重新分配?
-
看到目前用掉哪些子網了(例如 172.17.0.0/16、172.21.0.0/16)
-
選一個完全沒重疊的新網段來建你的 network,例如:
-
172.28.0.0/24 -
172.30.0.0/24 -
192.168.100.0/24 -
10.20.0.0/24
-
-
然後在你的
docker-compose.yml裡設定你自己的網段!
(一般建議用 172.x.x.x / 192.168.x.x / 10.x.x.x 這些 private 網段)
🔥 進階版(更詳細看)
如果你想要看得更細,例如看到每個 container 的 IP,可以打:
docker network inspect network-name
例:
docker network inspect bridge
docker network inspect pubsub-net
它會印出一個超大 JSON,裡面會列出:
-
網段 subnet
-
gateway
-
已連接的 container 名稱和 IP 地址
🛎️ 小結論
| 你想做什麼 | 指令 |
|---|---|
| 看所有 network 網段 | docker network inspect $(docker network ls -q) --format '{{.Name}}: {{(index .IPAM.Config 0).Subnet}}' |
| 詳細看某個 network | docker network inspect network-name |
【Docker】image tage 比較
當然可以!我們來一一比較你提到的這些 Docker image tag(以 Node.js 為例常見的 tag 形式),重點在於:
❗它們的差異主要在底層作業系統的發行版與體積,會影響相容性、功能完整度、啟動速度與安全更新週期。
✅ 比較總覽表
| Tag | 底層系統 | 體積(概估) | 相容性 | 套件完整度 | 適合場景 |
|---|---|---|---|---|---|
20.19.3 |
Debian(預設) | 🟠 中(~100MB) | ✅ 高 | ✅ 完整 | 開發 / 安全穩定部署 |
20.19.3-slim |
Debian(精簡) | 🟡 較小(~30–50MB) | ✅ 高 | 🟡 精簡套件 | 精簡部署,但仍需套件支援 |
20.19.3-bullseye |
Debian 11 (Bullseye) | 🟠 中 | ✅ 高 | ✅ 完整 | LTS 開發與部署 |
20.19.3-bullseye-slim |
Debian 11 (精簡) | 🟡 較小 | ✅ 高 | 🟡 精簡 | 比 slim 更指定版本的精簡版 |
20.19.3-bookworm |
Debian 12 (Bookworm) | 🟠 中 | ✅ 高 | ✅ 完整 | 新版 Debian,未來趨勢 |
20.19.3-bookworm-slim |
Debian 12 (精簡) | 🟡 較小 | ✅ 高 | 🟡 精簡 | 更「新」的精簡版本 |
20.19.3-alpine3.22 |
Alpine Linux 3.22 | 🟢 超小(~5MB) | ⚠️ 中 | ❌ 非常精簡 | 超小部署映像,需自行加套件 |
20.19.3-alpine |
最新 Alpine 版本 | 🟢 超小 | ⚠️ 中 | ❌ 非常精簡 | 最新 alpine,可能有相容性風險 |
🧩 詳細說明與比較依據
1️⃣ 無尾碼版本:20.19.3
-
預設會用 Debian(目前是 Bookworm 或 Bullseye)
-
最穩定、最多人支援的標準版本
-
套件支援完整,但 image 比 slim 大
2️⃣ slim 系列(slim, bullseye-slim, bookworm-slim)
-
相比完整版,移除了 man pages、locale 資料、編譯工具
-
仍然保留 Debian 的核心相容性
-
較小但仍易於除錯與擴充,比 alpine 安全穩
3️⃣ alpine 系列(alpine, alpine3.22)
-
超小型(~5MB),非常適合部署
-
缺點:需額外安裝 libc(有時需 glibc,不然會出現
segmentation fault) -
有些 Node 原生模組會無法正常編譯
-
建議只在熟悉 Alpine Linux 的情況下使用
4️⃣ bullseye vs. bookworm
-
bullseye= Debian 11(2021 發布) -
bookworm= Debian 12(2023 發布,目前最新版)
若你要做長期部署,bookworm 更新、更長期支援;但部分舊系統(如 AWS AMI、某些 lib)可能還用 bullseye。
✅ 實務建議
| 需求 | 建議使用 |
|---|---|
| 要最穩、開發與部署兼顧 | 20.19.3 或 20.19.3-bookworm |
| 部署在 Docker,但想減少容量 | 20.19.3-slim 或 20.19.3-bookworm-slim |
| 專案熟悉 Alpine、對大小敏感 | 20.19.3-alpine(但需額外測試) |
| 你想明確指定環境版本以便重建一致性 | 20.19.3-bullseye / bookworm / alpine3.22 等有版本號的更佳 |
📌 附加提醒
-
有些 Node 原生模組(如
bcrypt,sharp,canvas)在alpine上常常會遇到編譯問題。 -
slim不代表最小,只是比完整版小;真正小的是alpine。 -
不同 tag 最安全的選法是「明確指定版本與 base image」,可確保可重現性。
















