介绍
该泊坞窗平台允许开发者打包和运行应用程序的容器。容器是在共享操作系统上运行的隔离进程,为虚拟机提供了更轻巧的选择。尽管容器不是新事物,但它们提供的好处-包括进程隔离和环境标准化-随着越来越多的开发人员使用分布式应用程序体系结构而变得越来越重要。
在使用Docker构建和扩展应用程序时,通常是从为应用程序创建映像开始,然后可以在容器中运行该映像。该映像包括您的应用程序代码,库,配置文件,环境变量和运行时。使用映像可确保容器中的环境是标准化的,并且仅包含构建和运行应用程序所需的环境。
在本教程中,您将为使用Express框架和Bootstrap的静态网站创建应用程序映像。然后,您将使用该映像构建一个容器,并将其推送到Docker Hub以供将来使用。最后,您将从Docker Hub存储库中提取存储的映像并构建另一个容器,演示如何重新创建和扩展应用程序。
先决条件
要遵循本教程,您将需要:
- 按照此初始服务器设置指南设置一台Ubuntu 18.04服务器。
- 遵循如何在Ubuntu 18.04上安装和使用Docker的步骤1和2,在您的服务器上安装Docker。
- 按照以下有关由NodeSource管理的PPA进行安装的说明,已安装 Node.js和npm 。
- Docker Hub帐户。有关如何进行此设置的概述,请参阅本介绍如何开始使用泊坞枢纽。
第1步-安装应用程序依赖项
要创建图像,首先需要制作应用程序文件,然后可以将其复制到容器中。这些文件将包含应用程序的静态内容,代码和依赖项。
首先,在非root用户的主目录中为项目创建目录。我们将呼叫我们的node_project
,但您可以随意将其替换为其他内容:
mkdir node_project
导航到此目录:
cd node_project
这将是项目的根目录。
接下来,创建一个package.json
包含项目依赖项和其他标识信息的文件。使用nano
或您喜欢的编辑器打开文件:
nano package.json
添加有关项目的以下信息,包括其名称,作者,许可证,入口点和依赖项。确保用您自己的姓名和联系方式替换作者信息:〜/ node_project / package.json
{
"name": "nodejs-image-demo",
"version": "1.0.0",
"description": "nodejs image demo",
"author": "Sammy the Shark <sammy@example.com>",
"license": "MIT",
"main": "app.js",
"keywords": [
"nodejs",
"bootstrap",
"express"
],
"dependencies": {
"express": "^4.16.4"
}
}
该文件包括项目名称,作者和与其共享的许可证。Npm 建议使您的项目名称简短明了,并避免在npm Registry中重复。我们已在“许可证”字段中列出了MIT许可证,允许免费使用和分发应用程序代码。
此外,文件指定:
"main"
:应用程序的入口点app.js
。接下来,您将创建此文件。"dependencies"
:项目依赖性-在这种情况下,为Express 4.16.4或更高版本。
尽管此文件未列出存储库,但是您可以按照以下将存储库添加到package.json
文件中的准则进行添加。如果要对应用程序进行版本控制,这是一个很好的补充。
完成更改后,保存并关闭文件。
要安装项目的依赖项,请运行以下命令:
npm install
这将在package.json
文件中的项目目录中安装您列出的软件包。
现在,我们可以继续构建应用程序文件。
第2步-创建应用程序文件
我们将创建一个网站,向用户提供有关鲨鱼的信息。我们的应用程序将有一个主入口点,app.js
以及一个views
包含项目静态资产的目录。登陆页面index.html
将为用户提供一些初步信息,并提供指向页面的链接,其中包含更详细的鲨鱼信息sharks.html
。在views
目录中,我们将同时创建登录页面和sharks.html
。
首先,app.js
在主项目目录中打开以定义项目的路线:
nano app.js
文件的第一部分将创建Express应用程序和Router对象,并将基本目录和端口定义为常量:〜/ node_project / app.js
const express = require('express');
const app = express();
const router = express.Router();
const path = __dirname + '/views/';
const port = 8080;
该require
函数加载express
模块,然后我们将其用于创建app
和router
对象。该router
对象将执行应用程序的路由功能,并且当我们定义HTTP方法路由时,我们会将它们添加到该对象中,以定义我们的应用程序将如何处理请求。
文件的这一部分还设置了几个常量,path
以及port
:
path
:定义基本目录,该目录将是views
当前项目目录中的子目录。port
:告诉应用监听并绑定到port8080
。
接下来,使用router
对象设置应用程序的路由:〜/ node_project / app.js
...
router.use(function (req,res,next) {
console.log('/' + req.method);
next();
});
router.get('/', function(req,res){
res.sendFile(path + 'index.html');
});
router.get('/sharks', function(req,res){
res.sendFile(path + 'sharks.html');
});
该router.use
函数加载了一个中间件函数,该函数将记录路由器的请求并将其传递给应用程序的路由。这些在后续函数中定义,这些函数指定对基础项目URL的GET请求应返回index.html
页面,而对/sharks
路线的GET请求应返回sharks.html
。
最后,安装router
中间件和应用程序的静态资产,并告诉应用程序监听port 8080
:〜/ node_project / app.js
...
app.use(express.static(path));
app.use('/', router);
app.listen(port, function () {
console.log('Example app listening on port 8080!')
})
完成的app.js
文件将如下所示:〜/ node_project / app.js
const express = require('express');
const app = express();
const router = express.Router();
const path = __dirname + '/views/';
const port = 8080;
router.use(function (req,res,next) {
console.log('/' + req.method);
next();
});
router.get('/', function(req,res){
res.sendFile(path + 'index.html');
});
router.get('/sharks', function(req,res){
res.sendFile(path + 'sharks.html');
});
app.use(express.static(path));
app.use('/', router);
app.listen(port, function () {
console.log('Example app listening on port 8080!')
})
完成后保存并关闭文件。
接下来,让我们向应用程序中添加一些静态内容。首先创建views
目录:
mkdir views
打开登录页面文件index.html
:
nano views/index.html
将以下代码添加到文件中,该文件将导入Boostrap并创建一个巨型飞机组件,并带有指向更详细的sharks.html
信息页面的链接:〜/ node_project / views / index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>About Sharks</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link href="css/styles.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css">
</head>
<body>
<nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md">
<div class="container">
<button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
</button> <a class="navbar-brand" href="#">Everything Sharks</a>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav mr-auto">
<li class="active nav-item"><a href="/" class="nav-link">Home</a>
</li>
<li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="jumbotron">
<div class="container">
<h1>Want to Learn About Sharks?</h1>
<p>Are you ready to learn about sharks?</p>
<br>
<p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a>
</p>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-lg-6">
<h3>Not all sharks are alike</h3>
<p>Though some are dangerous, sharks generally do not attack humans. Out of the 500 species known to researchers, only 30 have been known to attack humans.
</p>
</div>
<div class="col-lg-6">
<h3>Sharks are ancient</h3>
<p>There is evidence to suggest that sharks lived up to 400 million years ago.
</p>
</div>
</div>
</div>
</body>
</html>
顶级导航栏允许用户在“ 主页”和“ 鲨鱼”页面之间切换。在navbar-nav
子组件中,我们使用Bootstrap的active
类向用户指示当前页面。我们还指定了到我们的静态页面的路由,这些路由与我们在中定义的路由匹配app.js
:〜/ node_project / views / index.html
...
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav mr-auto">
<li class="active nav-item"><a href="/" class="nav-link">Home</a>
</li>
<li class="nav-item"><a href="/sharks" class="nav-link">Sharks</a>
</li>
</ul>
</div>
...
另外,我们在巨型按钮中创建了指向鲨鱼信息页面的链接:〜/ node_project / views / index.html
...
<div class="jumbotron">
<div class="container">
<h1>Want to Learn About Sharks?</h1>
<p>Are you ready to learn about sharks?</p>
<br>
<p><a class="btn btn-primary btn-lg" href="/sharks" role="button">Get Shark Info</a>
</p>
</div>
</div>
...
标头中还有一个指向自定义样式表的链接:〜/ node_project / views / index.html
...
<link href="css/styles.css" rel="stylesheet">
...
我们将在此步骤结束时创建此样式表。
完成后保存并关闭文件。
有了应用程序登录页面后,我们可以创建我们的鲨鱼信息页面sharks.html
,该页面将为感兴趣的用户提供有关鲨鱼的更多信息。
打开文件:
nano views/sharks.html
添加以下代码,这些代码将导入Bootstrap和自定义样式表,并向用户提供有关某些鲨鱼的详细信息:〜/ node_project / views / sharks.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>About Sharks</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link href="css/styles.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css">
</head>
<nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md">
<div class="container">
<button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
</button> <a class="navbar-brand" href="/">Everything Sharks</a>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav mr-auto">
<li class="nav-item"><a href="/" class="nav-link">Home</a>
</li>
<li class="active nav-item"><a href="/sharks" class="nav-link">Sharks</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="jumbotron text-center">
<h1>Shark Info</h1>
</div>
<div class="container">
<div class="row">
<div class="col-lg-6">
<p>
<div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
</p>
</div>
<div class="col-lg-6">
<p>
<div class="caption">Other sharks are known to be friendly and welcoming!</div>
<img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
</p>
</div>
</div>
</div>
</html>
请注意,在此文件中,我们再次使用active
该类来指示当前页面。
完成后保存并关闭文件。
最后,创建自定义的CSS样式表,你已经链接到index.html
并sharks.html
通过首先创建一个css
在文件夹views
目录:
mkdir views/css
打开样式表:
nano views/css/styles.css
添加以下代码,这将为我们的页面设置所需的颜色和字体:〜/ node_project / views / css / styles.css
.navbar {
margin-bottom: 0;
}
body {
background: #020A1B;
color: #ffffff;
font-family: 'Merriweather', sans-serif;
}
h1,
h2 {
font-weight: bold;
}
p {
font-size: 16px;
color: #ffffff;
}
.jumbotron {
background: #0048CD;
color: white;
text-align: center;
}
.jumbotron p {
color: white;
font-size: 26px;
}
.btn-primary {
color: #fff;
text-color: #000000;
border-color: white;
margin-bottom: 5px;
}
img,
video,
audio {
margin-top: 20px;
max-width: 80%;
}
div.caption: {
float: left;
clear: both;
}
除了设置字体和颜色外,该文件还通过指定max-width
80%的a 来限制图像的大小。这样可以防止他们占用比页面上更多的空间。
完成后保存并关闭文件。
准备好应用程序文件并安装项目依赖项之后,就可以启动应用程序了。
如果您按照先决条件中的初始服务器设置教程进行操作,则将有一个活动防火墙,仅允许SSH通信。要允许流量进行端口8080
运行:
sudo ufw allow 8080
要启动该应用程序,请确保您位于项目的根目录中:
cd ~/node_project
使用以下命令启动应用程序node app.js
:
node app.js
将您的浏览器导航到。您将看到以下登录页面:http://your_server_ip:8080
单击获取鲨鱼信息按钮。您将看到以下信息页面:
现在,您已启动并运行了一个应用程序。准备就绪后,通过键入退出服务器CTRL+C
。现在,我们可以继续创建Dockerfile,该文件将允许我们根据需要重新创建和扩展此应用程序。
第3步—编写Dockerfile
您的Dockerfile指定执行时将包含在您的应用程序容器中的内容。使用Dockerfile可以定义容器环境,并避免依赖项或运行时版本之间的差异。
遵循这些有关构建优化容器的准则,我们将通过最大程度地减少图像层数并将图像功能限制于一个目的(使我们重新创建应用程序文件和静态内容)来使图像尽可能高效。
在项目的根目录中,创建Dockerfile:
nano Dockerfile
Docker映像是使用一系列相互构建的分层映像创建的。我们的第一步将是为我们的应用程序添加基本映像,该映像将构成应用程序构建的起点。
让我们使用该图像,因为在编写本文时,这是Node.js的推荐LTS版本。该图像来自Alpine Linux项目,将有助于我们减小图像尺寸。有关图像是否适合您的项目的更多信息,请参阅Docker Hub Node image页面的Image Variants部分下的完整讨论。node:10-alpine
alpine
alpine
添加以下FROM
指令以设置应用程序的基本映像:〜/ node_project / Dockerfile
FROM node:10-alpine
该图像包括Node.js和npm。每个Dockerfile必须以一条FROM
指令开头。
默认情况下,多克尔节点图像包括非超级节点的用户,您可以使用来避免运行你的应用程序容器为根。建议的安全实践是避免以root用户身份运行容器,并将容器内的功能限制为仅运行其进程所需的功能。因此,我们将使用节点用户的主目录作为应用程序的工作目录,并将其设置为容器内的用户。有关使用Docker Node映像时的最佳实践的更多信息,请参阅此最佳实践指南。
微调在容器我们的应用程序代码的权限,让我们创建node_modules
的子目录/home/node
与一起app
目录。创建这些目录将确保它们具有我们想要的权限,这在使用容器中创建本地节点模块时非常重要npm install
。除了创建这些目录,我们还将对节点用户设置所有权:〜/ node_project / Dockerfile
...
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
有关合并RUN
指令的实用程序的更多信息,请参见有关如何管理容器层的讨论。
接下来,将应用程序的工作目录设置为/home/node/app
:〜/ node_project / Dockerfile
...
WORKDIR /home/node/app
如果WORKDIR
未设置,则Docker将默认创建一个,因此最好进行显式设置。
接下来,复制package.json
和package-lock.json
(对于npm 5+)文件:〜/ node_project / Dockerfile
...
COPY package*.json ./
COPY
在运行npm install
或复制应用程序代码之前添加此指令可以使我们利用Docker的缓存机制。在构建的每个阶段,Docker将检查是否为该特定指令缓存了一层。如果我们更改package.json
,则将重建该层,但如果不这样做,则此指令将允许Docker使用现有的图像层,并跳过重新安装节点模块的过程。
为确保所有应用程序文件均由非根节点用户拥有,包括目录内容,请在运行之前node_modules
将用户切换至节点npm install
:〜/ node_project / Dockerfile
...
USER node
复制项目依赖项并切换我们的用户后,我们可以运行npm install
:〜/ node_project / Dockerfile
...
RUN npm install
接下来,将具有适当权限的应用程序代码复制到容器上的应用程序目录:〜/ node_project / Dockerfile
...
COPY --chown=node:node . .
这将确保应用程序文件由非根节点用户拥有。
最后,公开8080
容器上的端口并启动应用程序:〜/ node_project / Dockerfile
...
EXPOSE 8080
CMD [ "node", "app.js" ]
EXPOSE
不会发布端口,而是用作记录容器上哪些端口将在运行时发布的方式。CMD
运行命令以启动应用程序-在这种情况下为node app.js
。请注意,CMD
每个Dockerfile中应该只有一条指令。如果包含多个,则只有最后一个才会生效。
您可以使用Dockerfile做很多事情。有关说明的完整列表,请参考Docker的Dockerfile参考文档。
完整的Dockerfile如下所示:〜/ node_project / Dockerfile
FROM node:10-alpine
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
WORKDIR /home/node/app
COPY package*.json ./
USER node
RUN npm install
COPY --chown=node:node . .
EXPOSE 8080
CMD [ "node", "app.js" ]
完成编辑后,保存并关闭文件。
在构建应用程序映像之前,让我们添加一个.dockerignore
文件。以类似于.gitignore
file的方式工作,.dockerignore
指定不应将项目目录中的哪些文件和目录复制到容器中。
打开.dockerignore
文件:
nano .dockerignore
在文件内部,添加本地节点模块,npm日志,Dockerfile和.dockerignore
文件:〜/ node_project / .dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
如果您使用的是Git,则还需要添加.git
目录和.gitignore
文件。
完成后保存并关闭文件。
现在您可以使用该docker build
命令来构建应用程序映像了。使用-t
带有的标记,docker build
您可以为图像标记一个令人难忘的名称。因为我们要将映像推送到Docker Hub,所以我们在标签中包含Docker Hub用户名。我们会将图片标记为nodejs-image-demo
,但随时可以使用您自己选择的名称替换它。记住还要your_dockerhub_username
用您自己的Docker Hub用户名替换:
docker build -t your_dockerhub_username/nodejs-image-demo .
在.
指定的构建上下文是当前目录。
构建图像将需要一两分钟。完成后,检查图像:
docker images
您将看到以下输出:
OutputREPOSITORY TAG IMAGE ID CREATED SIZE
your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 8 seconds ago 73MB
node 10-alpine f09e7c96b6de 3 weeks ago 70.7MB
现在可以使用创建带有该图像的容器docker run
。我们将在此命令中包含三个标志:
-p
:这将在容器上发布端口,并将其映射到我们主机上的端口。我们将80
在主机上使用端口,但是如果您在该端口上运行其他进程,则可以随时根据需要进行修改。有关如何工作的更多信息,请参阅Docker文档中有关端口绑定的讨论。-d
:这将在后台运行容器。--name
:这使我们可以为容器起一个令人难忘的名称。
运行以下命令来构建容器:
docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo
容器启动并运行后,您可以使用以下命令检查正在运行的容器的列表docker ps
:
docker ps
您将看到以下输出:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e50ad27074a7 your_dockerhub_username/nodejs-image-demo "node app.js" 8 seconds ago Up 7 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo
在容器运行后,您现在可以通过将浏览器导航到来访问您的应用程序。您将再次看到您的应用程序登录页面:http://your_server_ip
现在,您已经为应用程序创建了映像,您可以将其推送到Docker Hub以供将来使用。
第4步-使用存储库处理图像
通过将应用程序映像推送到Docker Hub之类的注册表,您可以在构建和扩展容器时将其供以后使用。我们将通过将应用程序映像推送到存储库,然后使用该映像重新创建我们的容器来演示其工作原理。
推送映像的第一步是登录到在先决条件中创建的Docker Hub帐户:
docker login -u your_dockerhub_username
出现提示时,输入您的Docker Hub帐户密码。以这种方式登录将~/.docker/config.json
使用Docker Hub凭据在用户的主目录中创建一个文件。
现在,您可以使用之前创建的标签将应用程序映像推送到Docker Hub :your_dockerhub_username/nodejs-image-demo
docker push your_dockerhub_username/nodejs-image-demo
让我们通过销毁当前的应用程序容器和映像并使用存储库中的映像重建它们来测试映像注册表的实用程序。
首先,列出您正在运行的容器:
docker ps
您将看到以下输出:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e50ad27074a7 your_dockerhub_username/nodejs-image-demo "node app.js" 3 minutes ago Up 3 minutes 0.0.0.0:80->8080/tcp nodejs-image-demo
使用CONTAINER ID
输出中列出的内容,停止正在运行的应用程序容器。请务必将以下突出显示的ID替换为您自己的ID CONTAINER ID
:
docker stop e50ad27074a7
列出所有带有-a
标志的图像:
docker images -a
您将看到以下输出,其中包含图像的名称以及该图像和构建中的其他图像:your_dockerhub_username/nodejs-image-demo
node
OutputREPOSITORY TAG IMAGE ID CREATED SIZE
your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 7 minutes ago 73MB
<none> <none> 2e3267d9ac02 4 minutes ago 72.9MB
<none> <none> 8352b41730b9 4 minutes ago 73MB
<none> <none> 5d58b92823cb 4 minutes ago 73MB
<none> <none> 3f1e35d7062a 4 minutes ago 73MB
<none> <none> 02176311e4d0 4 minutes ago 73MB
<none> <none> 8e84b33edcda 4 minutes ago 70.7MB
<none> <none> 6a5ed70f86f2 4 minutes ago 70.7MB
<none> <none> 776b2637d3c1 4 minutes ago 70.7MB
node 10-alpine f09e7c96b6de 3 weeks ago 70.7MB
使用以下命令删除停止的容器和所有图像,包括未使用或悬挂的图像:
docker system prune -a
y
在输出提示时键入,以确认您要删除停止的容器和图像。请注意,这还将删除您的构建缓存。
现在,您已经删除了运行应用程序映像的容器和映像本身。有关删除Docker容器,映像和卷的更多信息,请参阅如何删除Docker映像,容器和卷。
删除所有映像和容器后,您现在可以从Docker Hub中提取应用程序映像:
docker pull your_dockerhub_username/nodejs-image-demo
再次列出您的图片:
docker images
您将看到您的应用程序图像:
OutputREPOSITORY TAG IMAGE ID CREATED SIZE
your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 11 minutes ago 73MB
现在,您可以使用第3步中的命令来重建容器:
docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo
列出您正在运行的容器:
docker ps
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f6bc2f50dff6 your_dockerhub_username/nodejs-image-demo "node app.js" 4 seconds ago Up 3 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo
再次访问以查看您正在运行的应用程序。http://your_server_ip
结论
在本教程中,您使用Express和Bootstrap创建了一个静态Web应用程序以及该应用程序的Docker映像。您使用此映像创建了一个容器,并将该映像推送到Docker Hub。从那里,您可以销毁映像和容器,并使用Docker Hub存储库重新创建它们。