Docker基础学习
写在前面:
应用部署很麻烦?哪里麻烦了啊!这么多年都是这样搞得,不要睁着眼睛乱说,有的时候自己找找原因,这么多年Linux命令敲得熟不熟,有没有认真工作?(手动狗头)
需求:我最近开发了一个前后端分离的GPU预约系统,这个系统涉及Redis数据库、MySQL数据库、Java环境、Nginx,这么多的东西如果直接部署到服务器上,其复杂度可想而知,何况还要处理各种版本冲突的问题。在这个需求背景下,使用Docker来简化部署就是自然而然的事情了。
下面记录我的Docker学习笔记,主要讲述Docker的基础用法,Docker的功能远不止于此。
Docker 安装和镜像加速
卸载旧版docker:
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
配置docker的yum库:
yum install -y yum-utils # 安装yum工具
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 配置docker的yum源
安装docker:
yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
启动和校验:
# 启动Docker
systemctl start docker
# 停止Docker
systemctl stop docker
# 重启
systemctl restart docker
# 设置开机自启
systemctl enable docker
# 执行docker ps命令,如果不报错,说明安装启动成功
docker ps
配置镜像加速:阿里云--产品--容器镜像服务ACR,找到镜像工具下的镜像加速器。
# 创建目录
mkdir -p /etc/docker
# 复制内容,注意把其中的镜像加速地址改成你自己的
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://xxxx.mirror.aliyuncs.com"]
}
EOF
# 重新加载配置
systemctl daemon-reload
# 重启Docker
systemctl restart docker
docker run命令:以部署MySQL数据库为例
当我们利用Docker安装应用时,Docker会自动搜索并下载应用镜像。Docker在运行镜像时候会创建一个隔离环境,我们称之为容器。
什么是镜像:
镜像包含应用本身,以及应用所需要的环境、配置和系统函数库。
镜像和容器的关系:
镜像只需要下载一次,可以启动多个应用,比如上面的命令,改个端口3307后,可以再跑一个MySQL,而且不用再下载镜像。
官方镜像仓库:hub.docker.com 可以类比maven和各种包管理器。
Docker命令的执行过程:
- docker run实际上是客户端,执行这条命令时候docker会向服务端通过RESTAPI发送请求,服务端由
docker daemon
守护进程、容器、镜像组成。
下面以运行MySQL容器的命令进行分析:
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
mysql
解读:
docker run
: 创建 + 运行一个容器-d
: 后台运行--name
: 指定唯一的容器名-p
端口映射3306 : 3306
,宿主机端口:容器内部端口- 容器内部的网络结构是对外不可访问的。可以通过
docker inspect 容器名
来查看。
-e KEY=VALUE
:设置环境变量 由镜像决定- 可以通过hub.docker.com 查看不同镜像的环境变量
mysql
: 运行镜像的名称- 完整写法: repository:tag,如 mysql:5.7
- 如果不写,默认是repository:latest
Docker常见命令
镜像相关
- 下载镜像:
docker pull
- 查看本地镜像:
docker images
- 删除本地镜像:
docker rmi
- 构建镜像,通过Dockerfile:
docker build
- 将镜像保存到本地:
docker save
- 加载镜像到本地:
docker load
(用的少) - 推送镜像到仓库:
docker push
容器相关
- 创建并运行容器:
docker run
,会创建容器 - 停止容器:
docker stop
,停止容器内部的进程,容器还在。 - 启动容器:
docker start
,启动容器内部的进程,不会创建容器,如果乱用docker run,会重复创建容器 - 查看容器运行状态:
docker ps
- 删除容器:
docker rm
- 查看日志:
docker logs
- 执行命令进入到容器内部:
docker exec
{% image https://obj.cagurzhan.cn/blog/post/docker/1.png, width=400px %}
案例:拉取Nginx镜像,创建并运行Nginx容器
# 需求1:拉取Nginx
docker pull nginx
docker images # 查看所有镜像
docker save -o nginx.tar nginx:latest # 打包
docker rmi nginx # 删除镜像
docker load -i nginx.tar # 重新加载镜像
docker run -d --name nginx -p 80:80 nginx # 运行
docker stop nginx # 暂停容器
docker ps # 查看运行中容器
docker ps -a # 查看所有容器
docker start nginx # 开启容器
docker ps
docker logs nginx # 查看日志
docker logs -f nginx # 以follow模式,一直查看日志
# 进入容器内部 -it 可交互终端
docker exec -it nginx bash
# 退出
exit
# *进入容器内部MySQL
docker exec -it mysql mysql -uroot -p
# 运行中容器不能删
docker stop mysql2
docker rm mysql2
# 也可以强制删除
docker rm mysql2 -f
进入容器内部,模拟了一台Linux文件系统,我们可以使用 docker exec -it 容器名 bash
进入。
Linux小技巧:.bashrc
vi ~/.bashrc
# 添加别名
alias dps='docker ps --format "table {{.ID}}\t{{.Images}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"'
# 退出vi后,让配置文件生效
source ~/.bashrc
数据卷
需求引入:Nginx部署静态资源
我们要通过Nginx部署静态资源,都需要将HTML文件放到了 /usr/share/nginx/html
目录下,但是难道我们每次都要通过exec命令进入容器?这显然是不现实的。
什么是数据卷(volume):
数据卷是一个虚拟目录,是容器内目录和宿主机目录之间的映射桥梁。
一旦我们实现数据卷的挂载,就会进行目录的双向绑定!
数据卷命令
- 创建数据卷:
docker volume create
- 查看所有数据卷:
docker volume ls
- 删除指定数据卷:
docker volume rm
- 查看某个数据卷的详情:
docker volume inspect
- 清楚数据卷:
docker volume prune
实际上,这些命令很少使用。我们一般在执行docker run命令的时候一并时间挂载。值得注意的是,数据卷的挂载是在容器创建的时候才能进行,如果容器已经创建了,是无法进行挂载的。
在docker run命令中,我们使用 -v 数据卷:容器内目录
来实现挂载。
检验:使用 docker volume inspect 数据卷
可以查看详细信息。
# 删除原来容器
docker rm nginx -f
# 创建容器并挂载数据卷
docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
案例2:MySQL容器的数据挂载
查看是否有数据卷挂载:
docker inspect mysql
发现有MySQL默认有数据卷挂载,但是数据卷的Name是一大串看不懂的字母。实际上,这种由容器自己创建的卷,叫做匿名卷。那么我们如何将数据卷挂载到自己指定的目录下?
其实还是 docker run -v
,只是语法不同而已。之前我们使用 docker run -v html:xxx
表示数据卷名字为html,这个时候会创建一个叫做html的数据卷。但如果我们加上 /
,即 docker run -v /html:xxx
,就会在html目录下挂载数据卷。一个符号的差别,效果是完全不同的!
回到MySQL,通过查看官方文档,下面我们分别在本地创建data init conf分别表示MySQL的数据、初始化脚本以及MySQL的配置文件。
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
-v /root/mysql/data:/var/lib/mysql \
-v /root/mysql/init:/docker-entrypoint-initdb.d \
-v /root/mysql/conf:/etc/mysql/conf.d \
mysql:5.7
Dockerfile
Dockerfile用于构建自己的镜像。构建镜像过程,就是将应用程序、系统函数库、运行配置等文件进行打包的过程。下面以构建一个自己的Java应用镜像为例。
镜像镜像结构:
- 基础镜像BaseImage:应用依赖的系统函数库、环境和配置等。如Ubuntu镜像。
- 层Layer:可以理解为添加安装包、依赖和配置的一系列步骤。如安装JRE、配置JRE环境变量、拷贝Jar包、设置启动脚本。
- 入口EntryPoint:镜像运行入口。一般是启动脚本。如java -jar xxx
什么是Dockerfile:
Dockerfile就是一个文本文件,这个文本文件包含一个个的指令,用指令来说明我们要执行什么操作来构建镜像,这就相当于一个菜谱!!
常见命令:
- FROM:指定基础镜像。
FROM centos:6
- ENV:设置环境变量。
ENV key value
- COPY:拷贝本地文件到容器目录。
COPY ./gpu.jar app.jar
- RUN:执行SHELL命令。
- EXPOSE:指定容器的监听端口。
EXPOSE 8080
- ENTRYPOINT:指定容器入口。
ENTRYPOINT java -jar xxx.jar
案例
FROM ubuntu:16.04
# 环境变量
ENV JAVA_DIR=/usr/local
# 拷贝jdk和jar包
COPY ./jdk8.tar $JAVA_DIR
COPY ./gpu.jar /tmp/app.jar
# 安装JDK
RUN cd $JAVA_DIR \ && tar -xf ./jdk8.tar \ && mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
当然我们并不需要每次都从最基础的镜像开始搭建起来,前面已经说了,这只是一些步骤的集合,那么这些繁琐的搭建环境的步骤已经有人总结为镜像了,以上Dockerfile可以简化成:
FROM openjdk:11-0-jre-buster
# 配置时区为东八区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY gpu.jar /app.jar
ENTRYPOINT ["java", "-jar","/app.jar"]
编写好Dockerfile后,利用下面命令构建自己的镜像:
docker build -t myImage:1.0 .
容器网络互连
我们刚才构建了nginx容器和MySQL容器,现在使用 docker inspect nginx
docker inspect mysql
查看容器内部,发现容器都有相同的内网IP字段。172.17.0.x
默认情况下,所有容器都是以bridge方式连接到Docker的一个虚拟网桥上,默认有一个docker0的虚拟网卡,172.17.0.1/16,16表示IP地址前两位是不能动的。所有容器都会以桥接的形式和docker0进行连接。
我们可以尝试进入一个容器,会发现他可以ping通另外一个容器,所以,如果我要进行容器之间的通信,我们可以直接对另外一个容器进行访问?
实际上这是不可以,假设服务器重新启动,这些内网IP可能会变化!解决此问题就需要用到自定义网络了。
常见网络命令
docker network ls # 查看网络
docker network create gpu # 创建网络
ip addr # 查看网卡
docker network connect gpu mysql # 将容器接入网络
docker run -d --name dd -p 8080:8080 --network gpu docker-demo # 创建容器就接入网络
docker inspect dd # 查看容器网络
docker exec -it dd bash
ping mysql # 用容器名访问
Docker Compose
Docker Compose 通过一个单独的docker-compose.yml文件来丁艺彝族相关联的容器应用,帮助我们实现多个相互关联docker容器的快速部署。
这里通过我的前后端分离GPU预约项目的docker-compose.yml文件进行学习:
version: "3.8" # docker compose版本,不修改
services: # 服务,每个服务是一个容器
mysql: # MySQL服务
image: mysql:5.7 # 镜像名
container_name: gpu-mysql # 容器名
ports: # 端口映射,可以多个
- "7779:3306"
environment: # 环境变量
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: gpu_monitor_7779
volumes: # 数据卷
- "./mysql/conf:/etc/mysql/conf.d"
- "./mysql/data:/var/lib/mysql"
- "./mysql/init:/docker-entrypoint-initdb.d"
networks: # 网络
- gpu-monitor-net
redis:
image: redis
container_name: gpu-redis
ports:
- "7776:6379"
networks:
- gpu-monitor-net
gpu-java:
build: # 构建
context: . # 当前目录下
dockerfile: Dockerfile
container_name: gpu-java
ports:
- "7777:7777"
networks:
- gpu-monitor-net
depends_on:
- mysql
nginx:
image: nginx
container_name: nginx
ports:
- "80:80"
volumes:
- "./nginx/nginx.conf:/etc/nginx/nginx.conf"
- "./nginx/html:/usr/share/nginx/html"
depends_on:
- gpu-java
networks:
- gpu-monitor-net
networks: # 创建网络
gpu-monitor-net: # 我理解为网络别名
name: gpu-java # 网络名
一键部署:
docker compose up -d
停止:
docker compose down # 会删除所有容器
命令:
- up 创建并启动所有服务容器
- down 停止并删除所有容器
- ps 列出所有启动
- logs 日志
- stop 暂停
- start 启动
- restart 重启
- top 查看运行进程
- exec 在指定容器中执行命令
- -f 指定compose文件路径
- -p 指定项目名
后记
本篇主要介绍docker的基础用法,后面如果实际需求中需要用到docker更高级的用法,或者有更优的Dockerfile文件模板和docker-compose实现方案,我都会发布新的博客进行更新。
- 感谢你赐予我前进的力量