Docker学习笔记(一)
暑期加入了沃天宇老师的实验室进行暑期的实习。在正式开始工作之前,师兄先让我了解一下技术栈,需要了解的有docker、k8s、springboot、springcloud。
仅以一系列博客记录一下自己学习的笔记。更多内容见Github
2021/7/5
参考资料
- docker官网: https://www.docker.com/
- 菜鸟教程: https://www.runoob.com/docker/docker-tutorial.html
- docker中文社区: https://www.docker.org.cn/
什么是docker?
在开始学习之前,先连带猜测给docker下一个定义,深入学习之后再回头来验证一下。
docker的作用应当是解决程序的环境问题,通过将环境和程序一起打包,使得能够方便地在不同计算机上恢复程序所需要的环境并运行程序。
这将解决开发过程中的环境不一致问题,大大降低部署时的环境问题。
相比于虚拟机,docker应当具有较高的效率和较低的代价。
安装
Linux参考:https://docs.docker.com/engine/install/
Windows参考:https://docs.docker.com/docker-for-windows/install/
注意Windows安装的时候需要打开WSL2功能,并且升级至最新版本。
运行官方demo
下载
首先,下载官方的示例app,将仓库中多余部分删除,保留app文件夹,并重命名为gov-sample-app
。
Dockerfile
创建Dockerfile
:
# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
- 第一句,
FROM
语句制定了初始的image,由于本地没有这个image,所以会从仓库中下载; - 第二句,
RUN
,应当是指定了一些需要使用的包,到这一步应当也是下载安装,作用应当是作为第一句引入的image的补充,添补额外的应用; - 第三句,看起来应当是指定一个运行目录,但是这个目录是容器内的目录还是
Build
然后切换到gov-sample-app
文件夹下运行:
docker build -t getting-started .
这个命令通过刚刚创建的Dockerfile来创建一个docker镜像。其中-t
指令为该镜像指定一个tag,我们运行这个镜像的时候可以通过这个tag来指代这个镜像。
此时先开始下载所需要的依赖,包括node:12-alpine
镜像和后面的python g++ make
。但是此时发现下载速度感人,需要进行换源。
因为我是在自己的阿里云服务器上进行实验,所以换了阿里的源https://cr.console.aliyun.com/cn-beijing/instances/mirrors。
重试发现仍不奏效,应当是第二步中apk add
的问题。简单搜索之后了解到,这里使用的镜像Alpine
其实是一个Linux发行版,因为其具有轻量化的优点,所以经常作为docker容器中使用的OS。因此,造成目前卡顿的原因是Alpine的包管理工具apk从仓库下载速度过慢,应当对apk进行换源。
在第一句FROM
后再加一行以进行换源:
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
虽然还是有些慢,但是好多了。最后Build成功,以下为输出:
第一次Biuld:
$ sudo docker build -t getting-started .
Sending build context to Docker daemon 4.659MB
Step 1/7 : FROM node:12-alpine
---> deeae3752431
Step 2/7 : RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
---> Running in bb1b2b2fca8d
Removing intermediate container bb1b2b2fca8d
---> 306dcbb9fb1b
Step 3/7 : RUN apk add --no-cache python g++ make
---> Running in b9c56ec12a6b
fetch http://mirrors.aliyun.com/alpine/v3.11/main/x86_64/APKINDEX.tar.gz
fetch http://mirrors.aliyun.com/alpine/v3.11/community/x86_64/APKINDEX.tar.gz
(1/21) Installing binutils (2.33.1-r1)
(2/21) Installing gmp (6.1.2-r1)
(3/21) Installing isl (0.18-r0)
(4/21) Installing libgomp (9.3.0-r0)
(5/21) Installing libatomic (9.3.0-r0)
(6/21) Installing mpfr4 (4.0.2-r1)
(7/21) Installing mpc1 (1.1.0-r1)
(8/21) Installing gcc (9.3.0-r0)
(9/21) Installing musl-dev (1.1.24-r3)
(10/21) Installing libc-dev (0.7.2-r0)
(11/21) Installing g++ (9.3.0-r0)
(12/21) Installing make (4.2.1-r2)
(13/21) Installing libbz2 (1.0.8-r1)
(14/21) Installing expat (2.2.9-r1)
(15/21) Installing libffi (3.2.1-r6)
(16/21) Installing gdbm (1.13-r1)
(17/21) Installing ncurses-terminfo-base (6.1_p20200118-r4)
(18/21) Installing ncurses-libs (6.1_p20200118-r4)
(19/21) Installing readline (8.0.1-r0)
(20/21) Installing sqlite-libs (3.30.1-r2)
(21/21) Installing python2 (2.7.18-r0)
Executing busybox-1.31.1-r10.trigger
OK: 212 MiB in 37 packages
Removing intermediate container b9c56ec12a6b
---> 30167547cd72
Step 4/7 : WORKDIR /app
---> Running in 28935dd6ee9b
Removing intermediate container 28935dd6ee9b
---> 9b4f5c2f993f
Step 5/7 : COPY . .
---> 9e6b8013cc6f
Step 6/7 : RUN yarn install --production
---> Running in ffcc047905f0
yarn install v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
info There appears to be trouble with your network connection. Retrying...
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 93.45s.
Removing intermediate container ffcc047905f0
---> 4f85bb916dc2
Step 7/7 : CMD ["node", "src/index.js"]
---> Running in 05444490086d
Removing intermediate container 05444490086d
---> 302e7c6b161a
Successfully built 302e7c6b161a
Successfully tagged getting-started:latest
到此,我们知道了,docker是需要一个os的,而一个常用的选择就是轻量的Linux发行版Alpine
。我们通过Dockerfile
来指定一个镜像如何打包,其中FROM
指令来指定一个初始的镜像,类似于OOP中的继承,我们在这个镜像的基础上,通过一系列指令来丰富其内容,创建我们的镜像。
但是,仍旧有遗留的问题:
-
RUN
和CMD
有什么区别,它们的参数看起来都是在Alpine
中运行的指令,这里的示例中它们采用了不同的格式,前者为shell格式,后者为exec格式,但是我试验后发现,将其格式对调,没有任何报错; -
COPY
到底拷贝了什么; -
WORKDIR
是哪儿来的?目前来看,官方包里的示例代码就是在app
文件夹下,但是我这里将其改名为gov-sample-app
,但是Build阶段并没有报错; - Build结束后应当有一个image,但是我并没有在文件夹中看到新的文件,这个镜像保存在哪里了?
- 在试验遗留问题(1)的时候,我发现此时执行Build时,让我之前头疼的
apk add
的过程被跳过了,直接Using cache
,并且可以发现,我只改动了Step6和7,对于前面的5步,除FROM
和COPY
外,都直接Using cache
了(从windows的log来看,FROM也直接使用了本地的cache中的内容),看来docker似乎是对Dockerfile中的大部分步骤都一步一步地进行了cache,这是怎么实现的?
第二次Build:
sudo docker build -t getting-started .
Sending build context to Docker daemon 4.659MB
Step 1/7 : FROM node:12-alpine
---> deeae3752431
Step 2/7 : RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
---> Using cache
---> 306dcbb9fb1b
Step 3/7 : RUN apk add --no-cache python g++ make
---> Using cache
---> 30167547cd72
Step 4/7 : WORKDIR /app
---> Using cache
---> 9b4f5c2f993f
Step 5/7 : COPY . .
---> fb639ed69103
Step 6/7 : RUN ["yarn", "install", "--production"]
---> Running in 3bd8d313084f
yarn install v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.9: The platform "linux" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 61.84s.
Removing intermediate container 3bd8d313084f
---> 9435838e03d2
Step 7/7 : CMD node src/index.js
---> Running in 49c60121400a
Removing intermediate container 49c60121400a
---> 7cb5bd6154b2
Successfully built 7cb5bd6154b2
Successfully tagged getting-started:latest
Run
docker run -dp 3000:3000 getting-started
其中-d
指令表示这个镜像放到后台去执行,此时不会进入到容器中,而是会直接detached,类似于screen的detached。
-p
表示要指定端口的映射,后面3000:3000
表示将宿主机的3000端口映射到容器的3000端口。经过试验,前面的是宿主机的端口号,后面的是容器的端口号。
最后,通过build时指定的tag名称来指定了需要运行的镜像的名字——getting-started
。可见,build打包好的镜像应该是保存在了一个docker的仓库里的,我们可以通过构建时指定的标签来指定它。
访问3000端口即可看到这个程序已经启动起来了:
通过docker ps
命令可以查看已经启动的容器,然后通过docker stop <CONTAINER ID>
来关闭已经detached的容器。
更新源码
按照官方文档,将src/static/js/app.js
文件第56行进行修改:
- <p className="text-center">No items yet! Add one above!</p>
+ <p className="text-center">You have no todo items yet! Add one above!</p>
此时再执行build
,查看3000端口,发现更改没有生效。再执行run
,获得如下报错:
docker: Error response from daemon: driver failed programming external connectivity on endpoint hardcore_colden (b2a0229cf98dca6b7a0f58a654b8263ee6e4718629116598128d1a2c633b733e): Bind for 0.0.0.0:3000 failed: port is already allocated.
报错显示宿主机的3000端口已经被占用,改用3001:
docker run -dp 3001:3000 getting-started
可以看到更改生效了,而此时访问3000仍旧可以看到旧的未更改的app。
此时,我们有以下这些命令:
构建一个镜像:
docker build -t <NAME>
运行一个镜像:
docker run -dp <HOST_PORT>:<CONTAINER_PORT> <NAME>
停止一个容器(但这个容器仍旧存在):
docker stop <CONTAINER>
删除一个容器(必须已停止):
docker rm <CONTAINER>
停止并删除一个容器:
docker rm -r <CONTAINER>
查看正在运行的:
docker ps
查看所有容器(包括已停止但未删除的):
docker ps -a
指定<CONTAINER>
的方式有三种:
-
CONTAINER ID
的全称; -
CONTAINER ID
的前缀子串,长度只要能够区分不同的容器即可; -
NAMES
,即容器的名字;
目前看来我已经启动的容器都有一个自然语言的NAME,但是目前不知道这个是怎么指定的,也不知道怎么生成的。
分享/发布镜像
首先,注册一个DockerHub账号,登录并创建一个新的仓库:
然后通过
docker login
来登录自己的DockerHub账号。在通过
docker tag getting-started <USER_ID>/getting-started
来创建一个应该与DockerHub对应的镜像,这个镜像是getting-started的复制。再通过
docker push <USER_ID>/getting-started
来将本地的镜像同步至DockerHub仓库。然后在另一台机器上(我在阿里云的自己的服务器上)登录后执行
docker pull <USER_ID>/getting-started
来将远程仓库的镜像同步下来。这一系列操作与git差不多,只不过git可以通过git push -u
来创建本地与远程仓库的关联,而docker通过docker tag
来创建一个镜像的复制表示对远程仓库的引用。
最后在服务器上执行run,来启动容器运行同步下来的进项。并且通过ssh的端口转发实现访问。
这里我将从远程仓库同步下来的镜像启动在3001端口,将未修改源码时build的镜像启动在3000端口。
通过docker images
可以查看目前所有的镜像。这里可以看到每个镜像都有一个TAG,默认值为latest
,我们可以通过在镜像名后面加:<tagname>
(注意冒号)来指定TAG。
小结
至此,我们简单尝试了docker的镜像构建、运行,以及远程仓库的同步、拉取的基本操作。