Doker 练习
ubuntu-24.04.2-live-server 版本
ubuntu docker 实战
js
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
需要外网
js
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get install docker-ce
sudo systemctl enable docker
sudo systemctl start docker
sudo docker version
sudo docker info
vim /etc/docker/daemon.json
{
"registry-mirrors": [
"https://registry.docker-cn.com"
]
}
systemctl daemon-reload
sudo systemctl restart docker
//查看 mirrors 生效
sudo docker info
普通用户
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.51/images/create?fromImage=docker.io%2Flibrary%2Fbusybox&tag=latest": dial unix /var/run/docker.sock: connect: permission denied
- echo $USER # 查看当前用户 liweihai
- 添加用户组 把当前用户添加到组
sudo groupadd docker sudo gpasswd -a $USER docker - #更新docker组
- newgrp docker
- ls -al /var/run/docker.sock // 查看权限
- sudo chmod 777 /var/run/docker.sock // 赋予权限
配置代理
js
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo vim /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://192.168.131.1:9665"
Environment="HTTPS_PROXY=http://192.168.131.1:9665"
Environment="NO_PROXY=localhost,127.0.0.1"
sudo systemctl daemon-reload
sudo systemctl restart docker
systemctl show --property=Environment docker
sudo docker pull redis:3.2 sudo docker inspect redis:3.2
容器互联
js
sudo docker run -d --name mysql -e MYSQL_RANDOM_ROOT_PASSWORD=yes mysql
sudo docker run -d --name webapp --link mysql webapp:latest
- 假设我们在 Web 应用中使用的是 JDBC 进行数据库连接的,我们可以这么填写连接
- String url = "jdbc:mysql://mysql:3306/webapp";
- 在这里,连接地址中的 mysql 就好似我们常见的域名解析,Docker 会将其指向 MySQL 容器的 IP 地址
暴露端口
sudo docker run -d --name mysql -e MYSQL_RANDOM_ROOT_PASSWORD=yes --expose 13306 --expose 23306 mysql:5.7 这里我们为 MySQL 暴露了 13306 和 23306 这两个端口, docker ps
通过别名连接
js
sudo docker run -d --name webapp --link mysql:database webapp:latest
- 在这里,我们使用 --link : 的形式,连接到 MySQL 容器,并设置它的别名为 database。当我们要在 Web 应用中使用 MySQL 连接时,我们就可以使用 database 来代替连接地址了
String url = "jdbc:mysql://database:3306/webapp";
配置网络
js
docker network list
sudo docker run -d --name mysql -e MYSQL_RANDOM_ROOT_PASSWORD=yes --network individual mysql:5.7
挂载
js
sudo docker run -d --name nginx -v /webapp/html:/usr/share/nginx/html nginx:1.12
/webapp/html:/usr/share/nginx/html 文件夹自动创建
sudo docker inspect nginx
查看 Mounts 节点
sudo docker run -d --name nginx -v /webapp/html:/usr/share/nginx/html:ro nginx:1.12
在挂载选项 -v 后再接上 :ro 就可以只读挂载了。
挂在临时节点
sudo docker run -d --name webapp --tmpfs /webapp/cache webapp:latest
挂载临时文件首先要注意它不是持久存储这一特性,在此基础上,它有几种常见的适应场景。
- 应用中使用到,但不需要进行持久保存的敏感数据,可以借助内存的非持久性和程序隔离性进行一定的安全保障。
- 读写速度要求较高,数据变化量大,但不需要持久保存的数据,可以借助内存的高读写速度减少操作的时间。
手动指定目录和非指定
js
sudo docker run -d --name nginx -v /webapp/html:/usr/share/nginx/html nginx:1.12
"Mounts": [
{
"Type": "bind",
"Source": "/webapp/html",
"Destination": "/usr/share/nginx/html",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
sudo docker run -d --name webapp -v /webapp/storage webapp:latest
"Mounts": [
{
"Type": "volume",
"Name": "2bbd2719b81fbe030e6f446243386d763ef25879ec82bb60c9be7ef7f3a25336",
"Source": "/var/lib/docker/volumes/2bbd2719b81fbe030e6f446243386d763ef25879ec82bb60c9be7ef7f3a25336/_data",
"Destination": "/webapp/storage",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
共用数据卷
js
sudo docker run -d --name webapp -v html:/webapp/html webapp:latest
备份和迁移数据卷
js
sudo docker create --name appdata -v /webapp/storage ubuntu
sudo docker run -d --name webapp --volumes-from appdata webapp:latest
打包
sudo docker run --rm --volumes-from appdata -v /backup:/backup ubuntu tar cvf /backup/backup.tar /webapp/storage
解压
sudo docker run --rm --volumes-from appdata -v /backup:/backup ubuntu tar xvf /backup/backup.tar -C /webapp/storage --strip
复杂挂在 多目录 多类型 多用途 --mount
$ sudo docker run -d --name webapp webapp:latest --mount 'type=volume,src=appdata,dst=/webapp/storage,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>' webapp:latest
保存和共享镜像
js
docker run -it --name busybox -t busybox:unstable-musl sh -c "export"
docker commit busybox
docker commit -m "Configured" busybox
docker tag f3d8e95b96aae busybox:1.0
docker tag busybox:1.0 busybox:2.0
docker images
docker commit -m "Upgrade test" busybox busybox:3.0
docker save busybox:1.0>/home/liweihai/busybox-1.0.tar
docker save busybox:1.0 -o /home/liweihai/busybox-1.1.tar
docker images
docker rmi busybox:1.0
docker load < /home/liweihai/busybox-1.1.tar
docker load -i /home/liweihai/busybox-1.1.tar
docker images
docker save -o /home/liweihai/images.tar busybox:2.0 busybox:1.0 busybox:3.0
docker export -o busybox:1.0 /home/liweihai/busybox-1.1.tar
docker import /home/liweihai/busybox-1.1.tar webapp:1.0
Dockerfile 指令
基础指令、控制指令、引入指令、执行指令、配置指令
js
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]
RUN <command>
RUN ["executable", "param1", "param2"]
CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2
COPY [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
EXPOSE <port> [<port>/<protocol>...]
VOLUME ["/data"]
当ENTRYPOINT 与 CMD 同时给出时,CMD 中的内容会作为 ENTRYPOINT 定义命令的参数,最终执行容器启动的还是 ENTRYPOINT 中给出的命令中
COPY和ADD 文件文件夹必须和Dockerfile同级目录
js
docker build ./webapp
docker build -t containerName .
docker build -t=containerName .
docker build -t webapp:latest -f ./webapp/a.Dockerfile ./webapp
docker build -t webapp:latest ./webapp
-f Dockerfile 文件的路径 -t 生成镜像的名称
js
FROM debian:stretch-slim
ARG TOMCAT_MAJOR
ARG TOMCAT_VERSION
RUN wget -O tomcat.tar.gz "https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz"
ARG指令定义为参数变量--build-arg 选项来设置参数变量
js
docker build --build-arg TOMCAT_MAJOR=8 --build-arg TOMCAT_VERSION=8.0.53 -t tomcat:8.0 ./tomcat
js
FROM debian:stretch-slim
ENV TOMCAT_MAJOR 8
ENV TOMCAT_VERSION 8.0.53
RUN wget -O tomcat.tar.gz "https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz"
-e或是--env 对环境变量的值进行修改或重新定义,采用 $ + NAME 这种形式来占位ENV 指令所定义的变量,永远会覆盖 ARG 所定义的变量
js
docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:5.7
js
RUN apt-get update; \
apt-get install -y --no-install-recommends $fetchDeps; \
rm -rf /var/lib/apt/lists/*;
上下两块代码一样区别为 合并 和拆分 一条多条
js
RUN apt-get update
RUN apt-get install -y --no-install-recommends $fetchDeps
RUN rm -rf /var/lib/apt/lists/*
合并到一条指令中,因减少了镜像层的数量,也减少了镜像构建过程中反复创建容器的次数,提高了镜像构建的速度。
$ sudo docker build --no-cache ./webapp 禁用缓存
| ENTRYPOINT | CMD | 实际执行 |
|:---------------------------------|:----------------------------|:--------------------------------------------------|---|
| ENTRYPOINT ["/bin/ep", "arge"] | | /bin/ep arge |
| ENTRYPOINT /bin/ep arge | | /bin/sh -c /bin/ep arge |
| | CMD ["/bin/exec", "args"] | /bin/exec args |
| | CMD /bin/exec args | /bin/sh -c /bin/exec args |
| ENTRYPOINT ["/bin/ep", "arge"] | CMD ["/bin/exec", "argc"] | /bin/ep arge /bin/exec argc |
| ENTRYPOINT ["/bin/ep", "arge"] | CMD /bin/exec args | /bin/ep arge /bin/sh -c /bin/exec args |
| ENTRYPOINT /bin/ep arge | CMD ["/bin/exec", "argc"] | /bin/sh -c /bin/ep arge /bin/exec argc |
| ENTRYPOINT /bin/ep arge | CMD /bin/exec args | /bin/sh -c /bin/ep arge /bin/sh -c /bin/exec args | |
ENTRYPOINT 和 CMD的不同。ENTRYPOINT 指令主要初始化, CMD 指令则定义容器中主程序的启动命令
创建容器时可以改写容器主程序的启动命令,而这个覆盖只会覆盖 CMD 中定义的内容,而不会影响 ENTRYPOINT 中的内容。
exec "$@"。在很多镜像的 ENTRYPOINT 脚本里,我们都会看到这条命令,其作用其实很简单,就是运行一个程序,而运行命令就是 ENTRYPOINT 脚本的参数。反过来,由于 ENTRYPOINT 脚本的参数就是 CMD 指令中的内容,所以实际执行的就是 CMD 里的命令。
Docker Compose
js
curl -L "https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
curl -L "https://github.com/docker/compose/releases/download/v2.37.0/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
//替换版本号替换变量
curl -L "https://github.com/docker/compose/releases/download/v2.37.0/docker-compose-linux-x86_64" --proxy http://192.168.131.1:9665 -o /usr/local/bin/docker-compose
使用代理
docker-compose version
docker-compose version 1.21.2, build a133471
docker-py version: 3.3.0
CPython version: 3.6.5
OpenSSL version: OpenSSL 1.0.1t 3 May 2016
使用pip 安装docker-compose
sudo apt update
sudo apt install python3 python3-pip -y
js
sudo mkdir dockerfile
cd dockerfile/
sudo vim docker-compose.yml
js
version: '3'
services:
webapp:
build: ./image/webapp
ports:
- "5000:5000"
volumes:
- ./code:/code
- logvolume:/var/log
links:
- mysql
- redis
redis:
image: redis:3.2
mysql:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=my-secret-pw
volumes:
logvolume: {}
js
sudo docker-compose up -d
sudo docker-compose down
$ sudo docker-compose -f ./compose/docker-compose.yml -p myapp up -d
选项
-f来修改识别Docker Compose配置文件,-p定义项目名。 日志 docker logs docker-compose logs
docker-compose 案例
js
version: "3"
services:
redis:
image: redis:3.2
networks:
- backend
volumes:
- ./redis/redis.conf:/etc/redis.conf:ro
ports:
- "6379:6379"
command: ["redis-server", "/etc/redis.conf"]
database:
image: mysql:5.7
networks:
- backend
volumes:
- ./mysql/my.cnf:/etc/mysql/my.cnf:ro
- mysql-data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=my-secret-pw
ports:
- "3306:3306"
webapp:
build: ./webapp
networks:
- frontend
- backend
volumes:
- ./webapp:/webapp
depends_on:
- redis
- database
nginx:
image: nginx:1.12
networks:
- frontend
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./webapp/html:/webapp/html
depends_on:
- webapp
ports:
- "80:80"
- "443:443"
networks:
frontend:
backend:
volumes:
mysql-data:
js
## ......
webapp:
build:
context: ./webapp
dockerfile: webapp-dockerfile
args:
- JAVA_VERSION=1.6
## ......
告知 Docker Compose 容器的先后启动顺序 depends_on 这个配置项
用 Map 的形式来定义 build,在这种格式下,我们能够指定更多的镜像构建参数,例如 Dockerfile 的文件名,构建参数等等
js
## ......
volumes:
mysql-data:
external: true
## ......
> 如果我们想把属于 Docker Compose 项目以外的数据卷引入进来直接使用,我们可以将数据卷定义为外部引入,通过 external 这个配置就能完成这个定义
js
networks:
frontend:
driver: bridge
ipam:
driver: default
config:
- subnet: 10.10.1.0/24
## ......
> 声明 frontend 和 backend 这两个网络最简单的方式。
Docker Compose 的每个服务配置里, ports 配置项,它是用来定义端口映射的。
docker-compose.yml 配置文件
js
version: "3"
networks:
frontend:
backend:
services:
redis:
image: redis:3.2
networks:
- backend
volumes:
- ../redis/redis.conf:/etc/redis/redis.conf:ro
- ../redis/data:/data
command: ["redis-server", "/etc/redis/redis.conf"]
ports:
- "6379:6379"
mysql:
image: mysql:5.7
networks:
- backend
volumes:
- ../mysql/my.cnf:/etc/mysql/my.cnf:ro
- ../mysql/data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: my-secret-pw
ports:
- "3306:3306"
phpfpm:
build: ./phpfpm
networks:
- frontend
- backend
volumes:
- ../phpfpm/php.ini:/usr/local/etc/php/php.ini:ro
- ../phpfpm/php-fpm.conf:/usr/local/etc/php-fpm.conf:ro
- ../phpfpm/php-fpm.d:/usr/local/etc/php-fpm.d:ro
- ../phpfpm/crontab:/etc/crontab:ro
- ../website:/website
depends_on:
- redis
- mysql
nginx:
image: nginx:1.12
networks:
- frontend
volumes:
- ../nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ../nginx/conf.d:/etc/nginx/conf.d:ro
- ../website:/website
depends_on:
- phpfpm
ports:
- "80:80"
在准备好这些之后,开始编写构建 PHP 镜像的 Dockerfile 文件。
js
FROM php:7.2-fpm
MAINTAINER You Ming <youming@funcuter.org>
RUN apt-get update \
&& apt-get install -y --no-install-recommends cron
RUN docker-php-ext-install pdo_mysql
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["php-fpm"]
js
#!/bin/bash
service cron start
exec "$@"
脚本的最后看到的是 exec " <math xmlns="http://www.w3.org/1998/Math/MathML"> @ " ' 这行命令。 ' @"` 这行命令。` </math>@"' 这行命令。'@ 是 shell 脚本获取参数的符号,这里获得的是所有传入脚本的参数,而 exec 是执行命令,直接执行这些参数。
$ sudo docker-compose -p website up -d
编写一个 compose 脚本,用来简化 docker-compose 的操作命令。
js
$ sudo ./bin/compose up -d
$ sudo ./bin/compose logs nginx
$ sudo ./bin/compose down
实战
js
version: "3"
networks:
backend:
mesh:
services:
mysql:
image: mysql:5.7
networks:
- backend
volumes:
- ../mysql/my.cnf:/etc/mysql/my.cnf:ro
- ../mysql/data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: my-secret-pw
ports:
- "3306:3306"
app:
build: ./spring
networks:
- mesh
- backend
volumes:
- ../app:/app
depends_on:
- mysql
sudo docker swarm init 来加入集群 docker swarm join-token 命令来获得管理节点的加入命令。
js
$ sudo docker swarm init
Swarm initialized: current node (t4ydh2o5mwp5io2netepcauyl) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-4dvxvx4n7magy5zh0g0de0xoues9azekw308jlv6hlvqwpriwy-cb43z26n5jbadk024tx0cqz5r 192.168.1.5:2377
$ sudo docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join --token SWMTKN-1-60am9y6axwot0angn1e5inxrpzrj5d6aa91gx72f8et94wztm1-7lz0dth35wywekjd1qn30jtes 192.168.1.5:2377
定义项目结构
-
app :用于存放程序工程,即代码、编译结果以及相关的库、工具等;
-
compose :用于定义 Docker Compose 项目;
-
mysql :与 MySQL 相关配置等内容;
-
redis :与 Redis 相关配置等内容;
-
tomcat :与 Tomcat 相关配置等内容。
js
# ./mysql/my.cnf
[mysqld_safe]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
skip-host-cache
skip-name-resolve
explicit_defaults_for_timestamp
bind-address = 0.0.0.0
port = 3306
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
log-error = /var/log/mysql/error.log
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
sql_mode = NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
lc-messages-dir = /usr/share/mysql
symbolic-links = 0
js
# ./redis/redis.conf
##...
################################## SECURITY ###################################
# Require clients to issue AUTH <PASSWORD> before processing any other
# commands. This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#
requirepass my-secret-pw
##...
js
要拿到 Tomcat 中的配置文件,我们需要先创建一个临时的 Tomcat 容器。
# docker run --rm -d --name temp-tomcat tomcat:8.5
js
# docker cp temp-tomcat:/usr/local/tomcat/conf/server.xml ./server.xml
# docker cp temp-tomcat:/usr/local/tomcat/conf/web.xml ./web.xml
# docker cp ./server.xml temp-tomcat:/usr/local/tomcat/conf/server.xml
# docker stop temp-tomcat
编写 Docker Compose 定义文件
js
version: "3"
services:
redis:
image: redis:3.2
volumes:
- ../redis/redis.conf:/etc/redis/redis.conf:ro
- ../redis/data:/data
command:
- redis-server
- /etc/redis/redis.conf
ports:
- 6379:6379
mysql:
image: mysql:5.7
volumes:
- ../mysql/my.cnf:/etc/mysql/my.cnf:ro
- ../mysql/data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: my-secret-pw
ports:
- 3306:3306
tomcat:
image: tomcat:8.5
volumes:
- ../app:/usr/local/tomcat/webapps/ROOT
ports:
- 80:8080
bash
在这个项目里,我将 Redis 和 MySQL 的数据存储目录,也就是 Redis 容器中的 /data 目录和 MySQL 容器中的 /var/lib/mysql 目录通过挂载的方式绑定到了宿主机上的目录中。 这么做的目的是为了让 Redis 和 MySQL 的数据能够持久化存储,避免我们在创建和移除容器时造成数据的流失。
同时,这种将数据挂载出来的方法,可以直接方便我们打包数据并传送给其他开发者,方便开发过程中进行联调。
在 Tomcat 这个服务中,我们将程序直接挂载到 webapps/ROOT 目录下,这样我们就能够借助 Tomcat 访问我们的应用了。 如果大家有多个项目,也可以进行适当调整,将它们挂载到 webapps 下面的子目录中,实现同时访问多个应用的目的。
另外,这里我还把 Tomcat 默认的 8080 端口映射到了宿主机的 80 端口上,这样便于我们直接通过地址访问网站,不需要经常人工补充端口号了。 # docker-compose -p javaweb -f ./compose/docker-compose.yml up -d
使用 Docker Compose 模拟 Zookeeper 集群
编写 docker-compose.yml
js
version: '3'
services:
zk1:
image: zookeeper:3.4
restart: always
hostname: zk1
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=0.0.0.0:2888:3888 server.2=zk2:2888:3888 server.3=zk3:2888:3888
ports:
- 2181:2181
zk2:
image: zookeeper:3.4
restart: always
hostname: zk2
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zk1:2888:3888 server.2=0.0.0.0:2888:3888 server.3=zk3:2888:3888
ports:
- 2182:2181
zk3:
image: zookeeper:3.4
restart: always
hostname: zk3
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zk1:2888:3888 server.2=zk2:2888:3888 server.3=0.0.0.0:2888:3888
ports:
- 2183:2181
js
server.1=0.0.0.0:2888:3888 server.2=zk2:2888:3888 server.3=zk3:2888:3888
在项目定义中,我们还注意到了 restart: always 这个配置,这个配置主要是用来控制容器的重启策略的。 启动项目 # ./bin/compose.sh up -d