Docker
开源的利用容器引擎,如果你是从事后端的开发者,置信对这门技术应该是理解或是相熟,而对于很多前端开发者,兴许只是停留在听过的阶段上,甚至不晓得是啥?或是会认为这是后端的技术,我不须要晓得,比如说我,还真的不晓得是什么,然而如果想成为一名资深前端,这部分空缺是须要填补上的。咸鱼也要有幻想嘛,兴许哪天能够跃龙门呢!
本文会通过构建带有web前端代码和mongoDB
数据库的全栈node.js
应用程序登程,来进一步理解Docker
以及它的用处。
什么是Docker
Docker 是一个开源的利用容器引擎,基于 Go 语言 并听从 Apache2.0 协定开源。Docker 能够让开发者打包他们的利用以及依赖包到一个轻量级、可移植的容器中,而后公布到任何风行的 Linux、Window 机器上,也能够实现虚拟化。
听不懂,是否艰深解释Docker前世今生?
2010年,几个搞IT的年轻人,在美国旧金山成立了一家名叫dotCloud
(搞容器技术)的公司。后果保持不上来,于是开源,后果火了,火了得从新起个牛逼的名字呀,于是Docker
就呈现了。
Docker
呈现前,如何模仿一个互相隔离
的零碎环境?答案就是虚拟机
,大家应该都不生疏,很多开发者电脑外面都会装VMWare
,通过它,咱们能够变出好几台子电脑
,一个装window11
、一个装CentOS
,装置上我喜爱的QQ
、微信
等软件,多个子电脑相互隔离、互不影响
,美滋滋!然而动不动就几个G、几十个G,磁盘吃不消呀,而且还启动慢。
后面说到了Docker
呈现前,虚拟机
在做环境隔离上是业界的网红,然而弊病是重
,而Docker
容器技术,其实也是一种虚拟化技术
,而且轻、快、一体化,只须要MB级甚至KB级,不像虚拟机
,须要模仿一个操作系统进去,Docker
只须要虚构一个小规模的环境(相似“沙箱”)。
不行,我要看数据比对,我才信,安顿....
Docker外围概念
上一大节,咱们理解了Docker是一种容器虚拟化技术,更轻、更快、更容易一体化,接下来咱们疾速理解一下它的外围概念后,再去进入咱们明天的主题,在编写代码才更好了解。
Docker
的三大外围概念:
- 镜像(Image)
- 容器(Container)
- 仓库(Repository)
以上关系图能反映出三者之间的关系,此处须要留神的是,咱们说Docker
是一种容器技术,然而Docker自身并不是容器,它是创立容器的工具,是利用容器引擎。
镜像
,也就是Docker镜像,是一个非凡的文件系统。它除了提供容器运行时所需的程序、库、资源、配置等文件外,还蕴含了一些为运行时筹备的一些配置参数(例如环境变量),同时镜像不蕴含任何动态数据。
咱们能够有很多镜像
,咱们想存起来,而后能够到任何中央去应用它创立容器
环境,那么就须要仓库
来存储,也就是Docker仓库
。
有怎么一个仓库
存在,那么所有人都能够往里面存镜像
么?不是的,如果寄存了个有问题的镜像
,那创立容器时候不就挂了么?所以须要有个负责对Docker镜像
进行治理的角色,就是Docker Registry服务(相似仓库管理员)
了。官网也提供了公共Registry服务
,就是Docker Hub
(有点像咱们的npm市场),外面寄存着很多高质量的官网镜像。
同时咱们还能够通过Dockfile文件来定制咱们的镜像,后续有介绍
通过下面的介绍,置信大家对Docker
应该也有了个大略的意识了,这里提供一些罕用的Docker
命令,
# 容器$ docker run // 创立并启动容器$ docker start // 启动容器$ docker ps // 查看容器$ docker stop // 终止容器$ docker restart // 查看容器$ docker attach // 进入容器$ docker exec // 查看容器$ docker export // 导出容器$ docker import // 导入容器快照$ docker rm // 删除容器$ docker log // 查看日志# 镜像$ docker search // 检索镜像$ docker pull // 获取镜像$ docker images // 列出镜像$ docker image ls // 列出镜像$ docker rmi // 删除镜像$ docker image rm // 删除镜像$ docker save // 导出镜像$ docker load // 导入镜像# Dockfile定制镜像以及罕用指令$ docker build // 构建镜像$ docker run // 运行镜像COPY // 复制文件ADD // 高级复制CMD // 容器启动指令ENV // 环境变量EXPOSE // 裸露接口# 服务$ docker -v // 查看docker的简要信息$ docker -version // 查看docker版本的简详细信息$ systemctl start docker // 启动docker$ systemctl stop docker // 敞开docker$ systemctl enable docker // 设置开机启动$ service docker restart // 重启docker服务$ service docker stop // 敞开docker服务
创立Hello-World容器
首先须要下载Docker
下载地址,我下载的是Window Docker Desktop
,接下来查看版本信息是否下载胜利,
我下载的是20.10.11
版本
而后拉取Docker Hub
的官网hello-world
镜像,
创立并执行容器,
这样咱们第一个容器就创立进去了,能够用用下面提到的命令行docker image ls / docker image prune
来查看或是删除没有用(进行运行容器时,它不会被删除,会帮忙下次下载安装速度放慢)的镜像,更多命令大家能够去试试!
创立Node程序
接下来咱们创立个Node程序,在前面教程介绍须要应用到,具体代码详情就不去做介绍了,能够查看以下server.js和package.json
:
const express = require("express");const app = express();const port = 8080;app.get("/", async (req, res) => { res.setHeader("Content-Type", "text/html"); res.status(200); res.send("<h1>你好呀!前端晚间课</h1>");});app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`);});
{ "name": "docker-example", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "start": "nodemon server.js" }, "author": "前端晚间课", "license": "ISC", "dependencies": { "express": "^4.17.2" }, "devDependencies": { "nodemon": "^2.0.15" }}
运行npm run start
,胜利跑起来了...
Node版本不同的困扰
针对下面的server.js
文件,咱们增加上面的代码:
// ... const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve("good"); }, 300); reject("bad");});myPromise.then(() => { console.log("this will never run");});
而后别离在Node < 15
和Node >= 15
运行,后果会失去两个不同的后果,
Node < 15
(node:764) UnhandledPromiseRejectionWarning: something happened(Use `node --trace-warnings ...` to show where the warning was created)(node:764) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)(node:764) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Node >= 15
node:internal/process/promises:218 triggerUncaughtException(err, true /* fromPromise */); ^[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "recipe could not be generated".] { code: 'ERR_UNHANDLED_REJECTION'}
会发现两个版本的执行后果会不统一,高版本(Node >= 15)会间接导致程序解体(ERR_UNHANDLED_REJECTION
),这是未解决的被回绝 Promise的谬误。
当初假如出于某种原因,此应用程序必须在 Node v14
或更早版本上运行能力工作(疏忽用try...catch
)。团队中的每个开发人员都必须筹备好在该环境中开发和运行,但咱们公司还有一个新的应用程序,要在 Node v17
上运行!
那这时应该如何去解决这个问题?答案:Docker
创立Dockerfile
上一大节,咱们介绍了两个不同版本的Node
引起的未解决的被回绝 Promise
的问题,咱们介绍如何应用Docker
去解决这个问题,其实也很简略,就是须要一个Node < 15
的运行环境来保障咱们的程序不会解体,
咱们在Docker Hub
能够搜寻到对于的Node
的镜像,而且还有很多版本信息可选。
当然咱们没必要间接应用docker pull node
来拉取node镜像
,后面咱们有提过Dockerfile
能够用来自定义定制镜像,它主动判断以后机器是否存在Node镜像
,没有的话再主动去Docker Hub
拉取,看一下咱们的Dockerfile
文件:
# 首先先抉择你须要的镜像,运行在alpine的node版本是当下最风行的FROM node:14-alpine3.12# 工作目录# 这是您你将在容器内的地位WORKDIR /usr/src/app# 通配符用于确保 package.json 和 package-lock.json 都被复制# COPY 源目录 容器的工作目录COPY package*.json ./# 装置利用依赖RUN npm install# 如果你正在构建用于生产的代码# RUN npm ci --only=production# 捆绑应用程序源COPY . .# 配置这个端口能够从容器内部拜访# 浏览器向 Node 应用程序发送 HTTP 申请所必须的EXPOSE 8080# CMD 在docker run 时运行# 就是执行shell npm run startCMD [ "npm", "run", "start"]
ok,咱们的Dockerfile
文件曾经创立胜利,对Dockerfile指令
在下面代码中正文也有做简略的介绍,更多具体指令用法能够到谷歌搜寻,但你可能会好奇下面的配置文件,为什么COPY
须要执行两次,最初的COPY . .
不是复制了整个目录,为啥下面还须要复制package*.json
?
Docker的层和缓存
COPY
两次是有必要的,因为Docker
领有layers
(层的个性),每执行一条指令都会基于上一次指令创立的图层根底上再创立一层图层,创立的图层会被缓存,只有产生扭转时才会再次从新创立,咱们再回过头来看Dockerfile
文件。
COPY package*.json ./
咱们创立了一个基于该文件内容的图层,而后再运行npm install
,这意味着除非咱们更改 package.json
,否则下次咱们构建 Docker
时将应用npm install
曾经运行的缓存层,咱们不用每次运行时都装置所有依赖项docker build。这将为咱们节俭大量工夫。
COPY . .
会查看咱们我的项目目录中的每个文件,因而该层将在任何文件更改时重建(除了package*.json
)。这正是咱们想要的。
构建利用容器
咱们再增加个.dockerignore
文件,相似咱们的.gitignore
,因为咱们不想复制这些文件呀。
node_modulesnpm-debug.log
所有准备就绪,咱们开始构建属于本人的镜像,
# 以以后我的项目目录为源目录,给镜像名个名叫做qianduanwanjianke$ docker build . -t qianduanwanjianke
再查看咱们创立的镜像存不存在?
创立镜像后,咱们当初筹备从镜像构建一个容器来运行咱们的应用程序:
# --name 咱们给容器名了个名,叫做qianduanwanjianke-container# -p标记将端口从咱们的主机(咱们的计算机)环境3001端口映射到容器环境的8080端口,当然也能够是8080:8080。docker run -p 3001:8080 --name qianduanwanjianke-container qianduanwanjianke
功败垂成,咱们拜访一下http://localhost:3001/
看看是否胜利,
结语
到这里,咱们通过一个Node 应用程序
切入来介绍Docker
,创立了咱们的第一个自定义 Docker 镜像和容器,并在其中运行咱们的应用程序!这是面向 Javascript 开发人员的 Docker 简介,由Node
程序切入,文章内容对于相熟Docker
的后端开发者来说可能是很相当容易的,对于咱们前端开发者来说应该是很好的入门教程。因为篇幅曾经很长了,我决定把Docker Volume
("连贯"起容器外部的程序正本与我的项目目录中的正本,更新同步) 以及引入数据库,剖析数据库托管服务器不同,如何创立拆散、Docker Compose
等内容搁置到下篇内容再做介绍。
过程中踩的坑
1、拉取镜像时报错,报错信息:error during connect: This error may indicate that the docker daemon is not running...
解决办法:
# 在Powershell 晋升拜访权限解决此问题cd "C:\Program Files\Docker\Docker"./DockerCli.exe -SwitchDaemon
2、创立利用容器的时候,执行docker build . -t my-node-app
, 报错信息:no matching manifest for windows/amd64 10.0.18363 in the manifest list entries
?
解决办法:
关上Docker Devlop
软件, settiong -> Docker Engine
,将experimental设置为true,重启Docker