docker
前言
最近在研究云开发,接触到了docker,就简单了解了一下,记录一下这几天实操的笔记。
docker是什么
网上有很多相关的资料,按照我的理解,docker最核心的就是镜像和容器,镜像实现了封装,容器实现了环境隔离。
部署项目
- vue
- nodejs
- mysql
准备工作
- 注册镜像仓库,我用的是
https://hub.docker.com/,也可以用阿里云的镜像仓库,用于保存我们的镜像 - 安装
docker,网上有很多教程,window安装比较麻烦(文末会提到一些注意事项),mac安装docker desktop即可,linux安装很简单。
镜像
我们从最基本的镜像打包开始,现在我有一个vue项目,准备部署到服务器上
- 在项目根目录创建
Dockerfile和default.conf文件,内容如下
# Dockerfile
# 必须,基于官方nginx镜像,为了部署服务
FROM nginx
# 非必须,说明
MAINTAINER website-library
# 移除默认nginx配置
RUN rm /etc/nginx/conf.d/default.conf
# 使用我们定义的nginx配置
ADD default.conf /etc/nginx/conf.d/
# 复制dist目录下的文件
COPY dist/ /usr/share/nginx/html/
# default.conf
server {
listen 80;
server_name localhost; # 修改为docker服务宿主机的ip
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
- 打包项目,执行命令
docker build -t 镜像名 .,不要忘记后面的.,镜像名改为你想要的名字 - 一个完整的镜像名应该是
[镜像仓库名]/[镜像名]:[tag],当然可以简写,当你需要push到镜像仓库时,镜像名是必须的;当你需要标示当前镜像的版本或其他信息,tag是必须的,tag如果省略,默认是latest - 例如我的镜像仓库名是chakcheung,这个镜像我希望可以看出来是版本1的,那么我在打包镜像的时候会一步到位,
docker build -t chakcheung/vuedemo:v1 .,当然后面是可以改的。 - 这时候执行命令
docker images或docker image ls,可以看到镜像列表 - 接下来我想测试一下镜像,执行命令
docker run -p 8080:80 -d --restart=always chakcheung/vuedemo:v1,如果运行成功,终端会反馈容器id - 执行命令
docker ps,可以看到容器id,如果没有,可能是发错错误,执行命令docker ps -a可以看到所有容器,包括未运行的,执行命令docker logs 容器id,可以打印容器执行的日志,方便定位错误。 - 如果执行成功,那可以打开浏览器,访问
localhost:8080,这是应该可以看到项目正常运行。 - 我们拆解一下运行镜像的命令
-p该参数表示将对服务器8080端口的访问,映射到容器的80端口,我们已经在容器中将项目部署在80端口-d表示后台运行--restart=always表示自动重启
推送镜像
- 完成了镜像的打包,接下来就可以push到我们的远程库
- 执行命令
docker login --username=仓库名,输入密码,登录成功会返回Login Succeeded - 执行命令
docker push chakcheung/vuedemo:v1,将镜像推送到仓库 - 如果在打包镜像的时候你没有把仓库名附上,可以执行命令
docker tag 原镜像 [仓库名]/镜像名:[tag],标记镜像,将其归入仓库 - 这时执行命令
docker image ls可以看到多了一个镜像,执行push操作将其推送到远程库即可 - 打开远程库的地址,或者打开
docker desktop,可以在镜像列表中看到我们刚推送的镜像
拉取镜像并部署服务
- 到服务器中,执行命令
docker pull chakcheung/vuedemo:v1,前提是你已经登录了 - 可以看到镜像列表多了一条记录,接下来我们要把这个镜像部署到服务器的
9500端口 - 执行
docker run -p 9500:80 -d --restart=always chakcheung/vuedemo:v1 - 执行
docker ps看是否运行成功 - 这时候就已经部署成功了,这时就可以访问服务器的
9500端口,看到部署后的项目了
docker-compose
- 按照上面的操作,我们可以把
nodejs项目和mysql都部署到服务器上 nodejs的Dockerfile也是大同小异,如下# 使用官方 Node.js 12 轻量级镜像. FROM node:12-slim # 定义工作目录 WORKDIR /usr/src/app # 将依赖定义文件拷贝到工作目录下 COPY package*.json ./ # 以 production 形式安装依赖 RUN npm install --only=production # 将本地代码复制到工作目录内 COPY . ./ # 启动服务 CMD [ "npm", "run", "start" ]mysql可以在服务器拉取官方镜像,执行命令docker run --name mysql-demo -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d --restart=always mysql启动即可- 你当然可以这样,一个镜像一个镜像地打包、推送、拉取、启动,然后在每次更新代码的时候,也是重复地打包、推送、拉取、删除容器、启动。但你有没有想过有什么工具可以帮你做这些事情,那就是
docker-compose docker-compose基于yml脚本,可以很好地管理我们的镜像和容器,文档可以自己去搜- 安装
docker-compose,windows和mac安装docker时会同时安装,linux需要自己安装 - 找一个目录,存储
yml文件,执行命令vim docker-compose.yml创建yml文件,文件名随意,内容如下
# 使用版本
version: "3"
# 服务列表
services:
vuedemo:
# 使用的镜像 也可以通过dockerfile指定打包脚本
image: chakcheung/vuedemo:v1
# 端口映射
ports:
- "9500:80"
mysql:
image: mysql
ports:
- "3306:3306"
# 文件夹映射
volumes:
- /data/mysql/data/:/var/lib/mysql/
- /data/mysql/conf/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf
# 环境变量
environment:
- MYSQL_ROOT_PASSWORD=123456
- LANG=C.UTF-8
backend:
image: chakcheung/backend
ports:
- "1333:1333"
- 然后,在
yml所在目录下执行命令docker-compose pull,这个命令会打包或拉取所有服务所需要的镜像,紧接着执行命令docker-compose up -d,该命令会根据yml启动服务- 当然,这两个命令都可以通过
-f指定yml文件 docker-compose up可以声明--force-recreate强制重建服务,否则如果镜像没有更新,服务不会重建
- 当然,这两个命令都可以通过
- 这时候,可以执行命令
docker ps -a看看服务的执行情况,应该三个服务都执行成功 - 以后我们代码更新了,只需要更新镜像,然后到服务器上,执行命令
docker-compose pull,docker-compose up -d就可以了,丝滑!
dockerize
上一步,你也可能滑不下去,出现部分服务启动失败到情况。有一种情况,假如backend项目启动的时候,尝试连接数据库,那很可能会连接失败并报错,也就是项目依赖的问题,网上有不同的解决办法,这里我们不妨试试dockerize这个工具集
dockerize提供一些参数,让我们可以等待某个服务启动完成后,执行操作- 首先整理一下项目依赖关系,
vuedemo基本上是不依赖其他两个的,backend因为启动的时候就初始化了mysql的连接,所以我们要等到mysql启动完毕之后,再启动backend,也就是我们要在backend这个容器的启动命令中,执行dockerize,我们修改一下yml文件,在backend中增加执行命令
backend:
image: chakcheung/backend
ports:
- "1333:1333"
command: ["dockerize", "-wait", "tcp://mysql:3306", "-timeout", "1800s", "npm", "run", "start:prod"]
-wait表示等待某个服务;-timeout表示多少秒后超时;后面则是成功后执行的命令- 但这时候直接执行,会发现
dockerize找不到,因为容器中不存在这个命令,我们要在容器中安装dockerize,这时候你可能会想到进到容器中安装,docker也提供了exec和attach方法让我们进到容器中,但这样做,以后每次更新我们都要重复去安装,那可不行,既然这个镜像没有,我们自己做个镜像!
制作镜像
这些是我学习过程中遇到的问题,也是自己想的解决办法,如果有更好的办法,请无情地嘲笑,然后教下我。
- 原来
backend是基于官方的node:12-slim,那我们可以基于这个镜像,封装一个带dockerize的镜像 - 执行命令
docker pull node:12-slim - 执行命令
dcoker run -ti node:12-slim /bin/bash,运行并进入容器 - 进入容器后,因为是mini版,要安装一些包,执行命令
apt-get updateapt-get upgradeapt-get wget
- 安装
dockerize
wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-alpine-linux-amd64-v0.6.1.tar.gz \
&& tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-v0.6.1.tar.gz \
&& rm dockerize-alpine-linux-amd64-v0.6.1.tar.gz
exit退出容器- 执行
docker commit -m "init commit" -a "chak" 容器id chakcheung/node:dockerize - 这时镜像列表会有一条新的记录,push到仓库就可以了
- 修改
backend的Dockerfile
# 使用封装的镜像
FROM chakcheung/node:dockerize
# 定义工作目录
WORKDIR /usr/src/app
# 将依赖定义文件拷贝到工作目录下
COPY package*.json ./
# 以 production 形式安装依赖
RUN npm install --only=production
# 将本地代码复制到工作目录内
COPY . ./
# 启动服务
CMD [ "npm", "run", "start:prod" ]
- 重新打包镜像并push,到服务器中,执行pull和up
- 完成后,执行命令
docker logs 容器id查看backend的运行日志,可以看到
2021/07/28 07:51:45 Waiting for: tcp://mysql:3306
2021/07/28 07:51:46 Problem with dial: dial tcp 172.19.0.3:3306: connect: connection refused. Sleeping 1s
2021/07/28 07:51:47 Problem with dial: dial tcp 172.19.0.3:3306: connect: connection refused. Sleeping 1s
2021/07/28 07:51:48 Connected to tcp://mysql:3306
- 在
mysql服务部署完成后,才会开始部署backend,这样就解决了服务依赖的问题
一些问题
windows安装docker
windows上,docker安装完成后一般会出现这个错误
error during connect: In the default daemon configuration on Windows, the docker client must be run with elevated privileges to connect.: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.24/containers/json: open //./pipe/docker_engine: The system cannot find the file specified.
好像是因为docker仅支持linux的内核,而网上的解决方法并不适用我,虽然很多人都说解决了,但一直都还是这个错误
# 网上的解决办法,切换容器
cd "C:\Program Files\Docker\Docker"
./DockerCli.exe -SwitchDaemon
因此我只能另外想办法,cmd和powershell都不能正常启动,那就试试linux的内核,刚好最近升级了系统,支持WSL2,于是安装了WSL2,并且安装了Ubuntu 20.04分发版,在Ubuntu终端下,成功执行docker命令
# 适用于 Linux 的 Windows 子系统安装指南 (Windows 10)
https://docs.microsoft.com/zh-cn/windows/wsl/install-win10#manual-installation-steps
# 适用于 Linux 的 Windows 子系统发行版包(Microsoft Store不可用时,可以在这下载)
https://docs.microsoft.com/zh-cn/windows/wsl/install-manual
mysql8 连接报错
mysql8以上的版本,连接时可能会出现这个错误
Client does not support authentication protocol requested by server; consider upgrading MySQL client
原因是MySQL8 之前的版本中加密规则是mysql_native_password,而在MySQL8之后,加密规则是caching_sha2_password,一般的解决方法是把mysql用户登录密码加密规则还原成mysql_native_password,因此你要么降低版本,要么修改加密规则,我选择保留8的版本
- 执行
docker exec -it 容器id /bin/bash进入容器,执行命令mysql -u root -p登录mysql
# 依次执行
ALTER USER 'root'@'%' IDENTIFIED BY '新密码' PASSWORD EXPIRE NEVER;
ALTER USER 'root'@'%' IDENTIFIED WITH MYSQL_NATIVE_PASSWORD BY '新密码';
# 重启
FLUSH PRIVILEGES;
- 退出,重新连接即可
- 但这么做,意味着如果
mysql的容器销毁了,重建的时候我们还得做一遍,这很麻烦,虽然mysql的镜像不会轻易重建 - 但我毕竟忍不了,所以还是按照上面解决
dockerize的方法一样,基于官方mysql做了一个自己的镜像
nginx的部署问题
部署过程中遇到一个问题,我希望在访问9502端口的web目录时,代理到我的vue项目,也就是
访问localhost:9502 或 localhost:9502/web 或 localhost:9502/web/时
代理到部署在9500端口的服务
# 这是我的配置
server {
listen 9502;
server_name localhost;
location / {
rewrite ^/$ http://$host:9502/web/;
}
location /web {
rewrite ^/web$ http://$host:9502/web/;
proxy_pass http://127.0.0.1:9500/;
}
}
- 一开始没整明白,以为
9502/web/代理到9500时,会去读取当前目录的index.html以及其他资源,后来试了几回才发现,当index.html读取其他资源的时候,实际上是向localhost:9502/xxx发起请求,所以一直读取不到 - 想明白了这一点,我把
vue.config.js的publicPath值改为/web,这样index.html读取其他资源的时候,是向localhost:9502/web/xxx发起请求,那自然就被代理到9500下了 - 没接触过就会比较陌生,还是要多看多学