前言
才知道掘金文章还有字数限制,只能分开发。
之前使用 Vue 全家桶开发了个人博客,并部署在阿里云服务器上,最近在学习 React,于是使用 React 开发重构了自己的博客。
-
旧 vue_blog:vue.cl8023.com
-
新 react_blog:cl8023.com
主要技术栈如下:
- 前台页面:Next.js 搭建服务端渲染页面,利于 SEO
- 后台管理界面:create-react-app 快速搭建
- 服务接口:Egg.js + Mysql
- 部署:Linux + Docker
React 搭建博客前后台部分,这里不会细讲,只会说说中间遇到的一些问题和一些解决方法,具体开发教程可参考 React Hooks+Egg.js实战视频教程-技术胖Blog开发。
部署部分这里会是重点讲解,因为也是第一次接触 Docker,这里只记录自己的学习心得,有不对的地方还请多多指教。
恰好本次项目里前台页面是 node 运行,后台界面是静态 HTML,服务接口需要连接 Mysql,我觉得 Docker 来部署这几种情况也是比较全面的例子了,可以给后来同学作为参考,内容比较啰嗦,希望能帮助后来的同学少走一点坑,因为有些是自己的理解,可能会有错误,还请大家指正,互相学习。
项目地址
clone 下来参照目录哦~
一、React 篇
博客前台使用 Next.js 服务端渲染框架搭建,后台管理界面使用 create-react-app 脚手架搭建,服务接口使用 Egg 框架(基于 Koa)。后台管理和服务接口没什么好说的,就是一些 React 基础知识,这里主要说下 Next.js 中遇到的一些问题。
项目目录:
blog:前台界面,Next.js
admin:后台管理界面,create-react-app 脚手架搭建
service:前后台服务接口
1. 获取渲染数据
因为是服务端渲染,所以页面初始数据会在服务器端获取后,渲染页面后返回给前端,这里有两个官方 API,getStaticProps
,getServerSideProps
,从名字可以稍微看出一点区别。(Next.js 9.3 版本以上,使用 getStaticProps
或 getServerSideProps
来替代 getInitialProps
。)
getStaticProps:服务端获取静态数据,在获取数据后生成静态 HTML 页面,之后在每次请求时都重用此页面
const Article = (props) => {
return ()
}
/* 也可
export default class Article extends React.Component {
render() {
return
}
}
*/
export async function getStaticProps(context) {
try {
const result = await axios.post(api.getArticleList)
return {
props: { articleList: result.data }, // will be passed to the page component as props
}
} catch (e) {
return {
props: { articleList: [] }, // will be passed to the page component as props
}
}
}
export default Article
getServerSideProps:每次请求时,服务端都会去重新获取获取生成 HTML 页面
const Article = (props) => {
return ()
}
/* 也可
export default class Article extends React.Component {
render() {
return
}
}
*/
export async function getServerSideProps(context) {
try {
const result = await axios.post(api.getArticleList)
return {
props: { articleList: result.data }, // will be passed to the page component as props
}
} catch (e) {
return {
props: { articleList: [] }, // will be passed to the page component as props
}
}
}
export default Article
可以看到两者用法是一样的。
开发模式下 npm run dev
,两者没什么区别,每次请求页面都会重新获取数据。
生产环境下,需要先npm run build
生成静态页面,使用 getStaticProps 获取数据的话就会在此命令下生产静态 HTML 页面,然后npm run start
,后面每次请求都会重用静态页面,而使用 getServerSideProps 每次请求都会重新获取数据。
返回数据 都是对象形式,且只能是对象,key 是 props,会传递到类或函数里面的 props。
博客这里因为是获取博客文章列表,数据随时可能变化,所以选用 getServerSideProps 。
这里使用 try,catch 捕获异常,防止获取数据失败或者后端接口报错,服务端渲染错误返回不了页面。
2. 页面加载后请求
还有一些数据,我们并不希望在服务端获取渲染到页面里,而是希望页面加载后再操作。
使用 React Hook,可以在 useEffect
中操作:
const Article = (props) => {
useEffect(async () => {
await axios.get('')
}, [])
return ()
}
export async function getServerSideProps(context) {
try {
const result = await axios.post(api.getArticleList)
return {
props: { articleList: result.data }, // will be passed to the page component as props
}
} catch (e) {
return {
props: { articleList: [] }, // will be passed to the page component as props
}
}
}
export default Article
这里注意 useEffect
第二个参数,代表是否执行的依赖。
- 不传第二个参数:每次 return 重新渲染页面时,useEffect 第一个参数函数都会执行
- 传参 [],如上:代表不依赖任何变量,只执行一次
- 传参 [value],数组,可以依赖多个变量:代表依赖 value 变量(state 中的值),只在 value 值改变时,执行 useEffect 第一个参数函数
使用 Class,可以在 componenDidMount
中操作:
export default class Article extends React.Component {
componenDidMount() {
await axios.get('')
}
render() {
return
}
}
export async function getServerSideProps(context) {
try {
const result = await axios.post(api.getArticleList)
return {
props: { articleList: result.data }, // will be passed to the page component as props
}
} catch (e) {
return {
props: { articleList: [] }, // will be passed to the page component as props
}
}
}
export default Article
3. 页面动画
页面进入、退出动画找到一个比较好用的库 framer-motion, www.framer.com/api/motion/
先改造一下 pages/_app.js,引入 framer-motion
npm install framer-motion -S
import { AnimatePresence } from 'framer-motion'
export default function MyApp({ Component, pageProps, router }) {
return (
<AnimatePresence exitBeforeEnter>
<Component {...pageProps} route={router.route} key={router.route} />
</AnimatePresence>
)
}
在每个页面里通过在元素标签前加 motion 实现动画效果,如 pages/article.js 页面
const postVariants = {
initial: { scale: 0.96, y: 30, opacity: 0 },
enter: { scale: 1, y: 0, opacity: 1, transition: { duration: 0.5, ease: [0.48, 0.15, 0.25, 0.96] } },
exit: {
scale: 0.6,
y: 100,
opacity: 0,
transition: { duration: 0.5, ease: [0.48, 0.15, 0.25, 0.96] },
},
}
const sentenceVariants = {
initial: { scale: 0.96, opacity: 1 },
exit: {
scale: 0.6,
y: 100,
x: -300,
opacity: 0,
transition: { duration: 0.5, ease: [0.48, 0.15, 0.25, 0.96] },
},
}
const Article = (props) => {
const { articleList, route } = props
const [poetry, setPoetry] = useState(null)
const getPoetry = (data) => {
setPoetry(data)
}
return (
<div className="container article-container">
<Head>
<title>学无止境,厚积薄发</title>
</Head>
<Header route={route} />
<div className="page-background"></div>
<div style={{ height: '500px' }}></div>
<Row className="comm-main comm-main-index" type="flex" justify="center">
<Col className="comm-left" xs={0} sm={0} md={0} lg={5} xl={4} xxl={3}>
<Author />
<Project />
<Poetry poetry={poetry} />
</Col>
<Col className="comm-center" xs={24} sm={24} md={24} lg={16} xl={16} xxl={16}>
<motion.div className="sentence-wrap" initial="initial" animate="enter" exit="exit" variants={sentenceVariants}>
<PoetrySentence staticFlag={true} handlePoetry={getPoetry} />
</motion.div>
<div className="comm-center-bg"></div>
<motion.div initial="initial" animate="enter" exit="exit" variants={postVariants} className="comm-center-content">
<BlogList articleList={articleList} />
</motion.div>
</Col>
</Row>
</div>
)
}
需要实现动画效果的元素标签前加上 motion,在传入 initial,animate,exit,variants 等参数,variants 中
const postVariants = {
initial: { scale: 0.96, y: 30, opacity: 0 },
enter: { scale: 1, y: 0, opacity: 1, transition: { duration: 0.5, ease: [0.48, 0.15, 0.25, 0.96] } },
exit: {
scale: 0.6,
y: 100,
opacity: 0,
transition: { duration: 0.5, ease: [0.48, 0.15, 0.25, 0.96] },
},
}
// initial 初始状态
// enter 进入动画
// exit 退出状态
// 不想有退出动画,不写 exit 变量即可
注意:这里使用 AnimatePresence 改造了 _app.js 后,每个页面都要使用到 motion,否则页面切换不成功,不想要动画的可以如下给默认状态即可:
const Article = (props) =>{
return (
<motion.div initial="initial" animate="enter" exit="exit">
...
</motion.div>
)
}
4. 页面切换状态
在 Next.js 中使用 import Link from 'next/link'
可以实现不刷新页面切换页面
import Link from 'next/link'
const BlogList = (props) => {
return (
<>
<Link href={'/detailed?id=' + item.id}>
<div className="list-title">{item.title}</div>
</Link>
</>
)
}
export default BlogList
因为是在服务端渲染,在点击 Link 链接时,页面会有一段时间没任何反应,Next.js 默认会在右下角有一个转动的黑色三角,但实在是引不起用户注意。
这里使用插件 nprogress,实现顶部加载进度条
npm install nprogress -S
还是改造 _app.js
import 'antd/dist/antd.css'
import '../static/style/common.less'
import { AnimatePresence } from 'framer-motion'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import Router from 'next/router'
NProgress.configure({
minimum: 0.3,
easing: 'ease',
speed: 800,
showSpinner: false,
})
Router.events.on('routeChangeStart', () => NProgress.start())
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())
export default function MyApp({ Component, pageProps, router }) {
return (
<AnimatePresence exitBeforeEnter>
<Component {...pageProps} route={router.route} key={router.route} />
</AnimatePresence>
)
}
主要使用到 next/router 去监听路由切换状态,这里也可以自定义加载状态。
5. 页面 CSS 加载失败
在 Next.js 开发模式下,当第一次进入某个页面时,发现当前页面样式加载失败,必须刷新一下才能加载成功。
next-css: Routing to another page doesn't load CSS in development mode
Cant change page with 'next/link' & 'next-css'
在 Github 上也查到相关问题,说是在 _app.js 都引入一下,但是我试了下,还是不行,不过好在这种情况只在开发模式下,生产模式下没什么问题,所以也就没在折腾了,就这样刷新一下吧。
6. React Hoos 中实现 setInterval
在 components/PoetrySentence.js 中实现动态写一句诗的效果,在 class 中可以同通过 setInterval 简单实现,但在 React Hoot 中每次 render 重新渲染后都会执行 useEffect,或者 useEffect 依赖[] 就又只会执行一次,这里就通过依赖单一变量加 setTimeout 实现。
在 components/PoetrySentence.js 中
import { useState, useEffect } from 'react'
import { RedoOutlined } from '@ant-design/icons'
import { getPoetry, formatTime } from '../util/index'
const PoetrySentence = (props) => {
const [sentence, setSentence] = useState('')
const [finished, setFinished] = useState(false)
const [words, setWords] = useState(null)
const { staticFlag, handlePoetry } = props // 是否静态展示
useEffect(
async () => {
if (words) {
if (words.length) {
setTimeout(() => {
setWords(words)
setSentence(sentence + words.shift())
}, 150)
} else {
setFinished(true)
}
} else {
let tmp = await todayPoetry()
if (staticFlag) {
setFinished(true)
setSentence(tmp.join(''))
} else {
setWords(tmp)
setSentence(tmp.shift())
}
}
},
[sentence]
)
const todayPoetry = () => {
return new Promise((resolve) => {
const now = formatTime(Date.now(), 'yyyy-MM-dd')
let poetry = localStorage.getItem('poetry')
if (poetry) {
poetry = JSON.parse(poetry)
if (poetry.time === now) {
handlePoetry && handlePoetry(poetry)
resolve(poetry.sentence.split(''))
return
}
}
getPoetry.load((result) => {
poetry = {
time: now,
sentence: result.data.content,
origin: {
title: result.data.origin.title,
author: result.data.origin.author,
dynasty: result.data.origin.dynasty,
content: result.data.origin.content,
},
}
handlePoetry && handlePoetry(poetry)
localStorage.setItem('poetry', JSON.stringify(poetry))
resolve(poetry.sentence.split(''))
})
})
}
const refresh = () => {
getPoetry.load((result) => {
const poetry = {
time: formatTime(Date.now(), 'yyyy-MM-dd'),
sentence: result.data.content,
origin: {
title: result.data.origin.title,
author: result.data.origin.author,
dynasty: result.data.origin.dynasty,
content: result.data.origin.content,
},
}
handlePoetry && handlePoetry(poetry)
localStorage.setItem('poetry', JSON.stringify(poetry))
if (staticFlag) {
setSentence(poetry.sentence)
} else {
setFinished(false)
setWords(null)
setSentence('')
}
})
}
return (
<p className="poetry-sentence">
{sentence}
{finished ? <RedoOutlined style={{ fontSize: '14px' }} onClick={() => refresh()} /> : null}
<span style={{ visibility: finished ? 'hidden' : '' }}>|</span>
</p>
)
}
export default PoetrySentence
useEffect 依赖变量 sentence,在 useEffect 中又去更改 sentence,sentence 更新后触发重新渲染,又会重新执行 useEffect,在 useEffect 中加上 setTimeout 延迟,刚好完美实现了 setInterval 效果。
7. node-sass
原本项目中使用的是 sass,但在后面 docker 部署安装依赖时,实在时太慢了,还各种报错,之前也是经常遇到,所以索性直接换成了 less,语法也差不多,安装起来省心多了。
二、Docker 篇
1. 什么是 Docker
Docker 是一个开源的应用容器引擎,可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。
2. 为什么要使用 Docker
对我而言,因为现在使用的是阿里云服务器,部署了好几个项目,如果服务器到期后,更换服务器的话,就需要将所有项目全部迁移到新服务器,每个项目又要去依次安装依赖,运行,nginx 配置等等,想想都头大。而使用 Docker 后,将单个项目与其依赖打包成镜像,镜像可以在任何 Linux 中生产一个容器,迁移部署起来就方便多了。
其他而已,使用 Docker 可以让开发环境、测试环境、生产环境一致,并且每个容器都是一个服务,也方便后端实现微服务架构。
3. 安装
Docker 安装最好是参照官方文档,避免出现版本更新问题。docs.docker.com/engine/inst… 英文吃力的,这两推荐一款神奇词典 欧陆词典,哪里不会点哪里,谁用谁说好。
Mac 和 Windows 都有客户端,可以很简单的下载安装,另外 Window 注意区分专业版、企业版、教育版、家庭版
因为我这里使用的是阿里云 Centos 7 服务器,所以简单介绍一下在 Centos 下的安装。
Centos 安装 Docker
首先若已经安装过 Docker,想再装最新版,先协助旧版
$ sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
有三种安装方式:
- Install using the repository
- Install from a package
- Install using the convenience script
这里选择官方推荐的第一种方式安装 Install using the repository。
1、SET UP THE REPOSITORY
安装 yum-utils 工具包,设置存储库
$ sudo yum install -y yum-utils
$ sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
2、安装 docker
$ sudo yum install docker-ce docker-ce-cli containerd.io
这样安装的是最新的版本,也可以选择指定版本安装
查看版本列表:
$ yum list docker-ce --showduplicates | sort -r
Loading mirror speeds from cached hostfile
Loaded plugins: fastestmirror
Installed Packages
docker-ce.x86_64 3:20.10.0-3.el7 docker-ce-stable
docker-ce.x86_64 3:20.10.0-3.el7 @docker-ce-stable
docker-ce.x86_64 3:19.03.9-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.8-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.7-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.6-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.5-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.4-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.3-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.2-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.14-3.el7 docker-ce-stable
......
选择指定版本安装
$ sudo yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io
安装完成,查看版本
$ docker -v
Docker version 20.10.0, build 7287ab3
3、启动 docker
$ sudo systemctl start docker
关闭 docker
$ sudo systemctl stop docker
重启 docker
$ sudo systemctl restart docker
4. 镜像 Image
**Docker 把应用程序及其依赖,打包在 image 文件里面。**只有通过这个文件,才能生成 Docker 容器(Container)。image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。
image 文件是通用的,一台机器的 image 文件拷贝到另一台机器,照样可以使用。一般来说,为了节省时间,我们应该尽量使用别人制作好的 image 文件,而不是自己制作。即使要定制,也应该基于别人的 image 文件进行加工,而不是从零开始制作。
官方有个镜像库 Docker Hub,很多环境镜像都可以从上面拉取。
4.1 查看镜像
$ docker images
或者
$ docker image ls
刚安装完 docker,是没有任何镜像的
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
只查看全部镜像 id
$ docker images -q
# 或
$ docker image ls -q
4.2 下载镜像
这里我们尝试从官方库下载一个 nginx 镜像,镜像有点类似与 npm 全局依赖,拉取后,后面所有需要使用的 nginx 的镜像都可以依赖此 nginx,不用再重新下载,刚开始学习时,我还以为每个使用到 nginx 的镜像都要重新下载呢。
下载 nginx 镜像 hub.docker.com/_/nginx
$ docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
6ec7b7d162b2: Pull complete
cb420a90068e: Pull complete
2766c0bf2b07: Pull complete
e05167b6a99d: Pull complete
70ac9d795e79: Pull complete
Digest: sha256:4cf620a5c81390ee209398ecc18e5fb9dd0f5155cd82adcbae532fec94006fb9
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest ae2feff98a0c 13 hours ago 133MB
docker images 查看刚刚安装的 nginx 镜像,有 5 个title,分别为镜像名称,标签,id,创建时间,大小,其中 TAG 标签默认为 latest 最新版,如果下载指定版本,可以 : 后跟版本号
$ docker pull nginx:1.19
4.3 删除镜像
删除镜像可以使用如下命令
$ docker rmi [image]
或者
$ docker image rm [image]
[image] 可以是镜像名称+标签,也可以是镜像 id,ru
$ docker rmi nginx:latest
$ docker rmi ae2feff98a0c
删除所有镜像
$ docker rmi $(docker images -q)
删除所有 none 镜像
后面有些操作会重复创建相同的镜像,原本的镜像就会被覆盖变为 ,可以批量删除
$ docker rmi $(docker images | grep "none" | awk '{print $3}')
4.4 制作镜像
上面我们下载了 nginx 镜像,但是要想运行我们自己的项目,我们还要制作自己项目的镜像,然后来生成容器才能运行项目。
制作镜像需要借助 Dockerfile 文件,以本项目 admin 后台界面为例(也可以任何 html 文件),因为其打包后只需使用到 nginx 即可访问。
先在 admin 下运行命令 npm run build
打包生成 build 文件夹,下面包好 index.html 文件,在 admin/docker 文件夹下创建 Dockerfile 文件,内容如下
FROM nginx
COPY ../build /usr/share/nginx/html
EXPOSE 80
将 build,docker 两个文件夹放在服务器同一目录下,如 /dockerProject/admin
├─admin
└─build
└─index.html
└─docker
└─Dockerfile
在 docker 目录下运行命令
$ docker build ./ -t admin:v1
Sending build context to Docker daemon 4.096kB
Step 1/3 : FROM nginx
---> ae2feff98a0c
Step 2/3 : COPY ../build /usr/share/nginx/html
COPY failed: forbidden path outside the build context: ../build ()
./ 基于当前目录为构建上下文, -t 指定制作的镜像名称。
可以看到上面报错了,
The path must be inside the context of the build; you cannot ADD ../something/something, because the first step of a docker build is to send the context directory (and subdirectories) to the docker daemon.
上面大意是确定构建上下文后,中间的一些文件操作就只能在当前上下文之间进行,有两种方式解决
-
Dockfile 与 build 同目录
├─admin └─build └─index.html └─Dockerfile
Dockerfile:
FROM nginx COPY ./build /usr/share/nginx/html EXPOSE 80
在 admin 目录下执行命令
$ docker build ./ -t admin:v1 Sending build context to Docker daemon 3.094MB Step 1/3 : FROM nginx ---> ae2feff98a0c Step 2/3 : COPY ./build /usr/share/nginx/html ---> Using cache ---> 0e54c36f5d9a Step 3/3 : EXPOSE 80 ---> Using cache ---> 60db346d30e3 Successfully built 60db346d30e3 Successfully tagged admin:v1
-
依然将 Dokcerfile 放入 docker 中统一管理
├─admin └─build └─index.html └─docker └─Dockerfile
Dockerfile:
FROM nginx COPY ./build /usr/share/nginx/html EXPOSE 80
在 admin 目录下执行命令
$ docker build -f docker/Dockerfile ./ -t admin:v1 Sending build context to Docker daemon 3.094MB Step 1/3 : FROM nginx ---> ae2feff98a0c Step 2/3 : COPY ./build /usr/share/nginx/html ---> Using cache ---> 0e54c36f5d9a Step 3/3 : EXPOSE 80 ---> Using cache ---> 60db346d30e3 Successfully built 60db346d30e3 Successfully tagged admin:v1
注意这里的 ./build 路径。-f (-file)指定一个 Dockfile 文件,./ 以当前路径为构建上下文,所以 build 路径还是 ./build
上面使用到了 Dockerfile 文件,因为内容比较少,这里先不介绍,后面部署 Next.js 时在稍作说明。
5. 容器 Container
上面生成了 admin:v1 镜像,我们查看一下
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
admin v1 60db346d30e3 51 minutes ago 136MB
nginx latest ae2feff98a0c 14 hours ago 133MB
可以看到多了 admin:v1 镜像,且在上面构建镜像时步骤 Step 1 ,速度很快,直接使用了之前下载的 nginx 镜像,如果之前没下载,这里就会去下载。
项目运行在容器内,我们需要通过一个镜像创建一个容器。
5.1 查看容器
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
或
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
这两个命令只显示正在运行的容器,报错停止的都不会显示,加上 -a (--all) 参可以显示全部
$ docker container ls -a
$ docker ps -a
只查看所有容器 id
$ docker ps -aq
5.2 生成容器
这里我们通过 admin:v1 来生成一个容器
$ docker create -p 9001:80 --name admin admin:v1
- -p:端口映射,宿主机(服务器) : 容器,9001:80 代表宿主机 9000 端口可以访问到容器的 80 端口
- --name:生成的容器名称,唯一值
- admin:v1:使用的镜像,标签
:v1
默认为:latest
还有很多参数,可自行了解 docs.docker.com/engine/refe…
生成容器后,咱们来看看
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8d755bab5c73 admin:v1 "/docker-entrypoint.…" 5 minutes ago Created admin
可以看到容器已经生成,但还没有运行,所有使用 docker ps
是看不到的
运行容器:docker start [container iD]
,【】里面可以使用容器 ID,也可以使用容器名称,都是唯一的
$ docker start admin
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8d755bab5c73 admin:v1 "/docker-entrypoint.…" 8 minutes ago Up 3 seconds 0.0.0.0:9001->80/tcp admin
容器已经运行,此时通过服务器ip + 9001 端口(Mac、Windows 直接 localhost:9001)即可访问到容器内部。
以上生成容器,运行容器也可以一条命令
$ docker run -p 9001:80 --name admin admin:v1
5.3 删除容器
删除容器可以使用如下命令
$ docker rm admin # id 或 name
如果容器在运行中,要先停止容器
$ docker stop admin
或者强制删除
$ docker rm -f admin
停止所有容器
$ docker stop $(docker ps -aq)
删除所有容器
$ docker rm $(docker ps -aq)
停止并删除所有容器
$ docker stop $(docker ps -aq) & docker rm $(docker ps -aq)
5.4 容器日志
运行容器时如果失败,可以查看日志定位错位
$ docker logs admin
5.5 进入容器内部
容器就像一个文件系统,我们也可以进去查看里面的文件,使用以下命令进入容器内部
$ docker exec -it admin /bin/sh
-i
参数让容器的标准输入持续打开,--interactive-t
参数让 Docker 分配一个伪终端,并绑定到容器的标准输入上, --tty- admin: 容器 id 或名字
进入容器内部后,可以使用 Linux 命令访问内部文件
$ ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
$ cd usr/share/nginx/html
$ ls
50x.html asset-manifest.json favicon.ico index.html manifest.json robots.txt static
进入 nginx 默认 html 目录 usr/share/nginx/html,可以看到我们通过 Dockfile 拷贝过来的文件
6. docker-compose
通过上面可以发现每次制作镜像,生成容器,运行容器,都要输入很多命令,实在是很不方便,如果只要一个简单的命令就能完成就好了,docker-compose 就可以实现,当然,这只是它很小的一部分功能。
官方简介如下:
Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过Compose,您可以使用 YAML 文件来配置应用程序的服务。然后,使用一个命令,就可以从配置中创建并启动所有服务。
使用Compose基本上是一个三步过程:
- 使用 Dockerfile 定义应用程序的环境,以便可以在任何地方复制它。
- 在 docker-compose.yml 中定义组成您的应用程序的服务,以便它们可以在隔离的环境中一起运行。
- 运行 docker-compose up,然后 Compose 启动并运行整个应用程序。
6.1 安装 docker-compose
参考官方文档 Install Docker Compose ,这里简单介绍 Linux 安装
-
运行命令
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
若是安装慢,可以用 daocloud 下载
sudo curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
-
添加可执行权限
sudo chmod +x /usr/local/bin/docker-compose
-
检查是否安装完成
docker-compose --version
6.2 docker-compose.yml
docker-compose.yml 是 docker-compose 运行时使用文件,里面配置了镜像和容器一些参数,这里来实现上面创建镜像,生成容器,运行容器。
version: '3'
services:
admin:
build:
context: ../
dockerfile: ./docker/Dockerfile
image: admin:v1
ports:
- 9001:80
container_name: admin
配置参数有很多 docs.docker.com/compose/com…,官网可以详解,这里以及后面只说说用到的一些配置。
- varsion:可选 1,2,2.x,3.x
- services:服务组
- admin:服务名称,唯一,多个 docker-compose.yml 有相同名称的,下面的容器会覆盖
- build:构建参数,如果 docker-compose.yml、Dockfile 都和 built 文件加目录,可直接 ./ ,当前构建上下文,当前 Dockerfile;如果 docker-compose.yml、Dockfile 都放在 docker 文件夹下,则需指定构建上下文 context 和 dokcerfile
- context:构建上下文
- dockerfile:指定 dockerfile 路径
- image:指定使用的镜像,如果镜像存在,会直接使用镜像,否则的话通过上面的 dockerfile 构建
- ports:端口映射,可多个。文件中 - 就代表参数是数组形式,可以多个
- container_name:容器名字,若不指定,这默认为 当前目录_admin_index (admin:服务名,index:数字,累加,一个服务可以有可以容器,不同 docker-compose.yml 里有相同服务)
- admin:服务名称,唯一,多个 docker-compose.yml 有相同名称的,下面的容器会覆盖
将 docker-compose.yml 放入 docker 目录下
├─admin
└─build
└─index.html
└─docker
└─Dockerfile
└─docker-compose.yml
在 docker 目录下运行
$ docker-compose up -d --build
- -d:代表在后台守护运行,不加 -d 的话,会显示构建过程,最后完成只能 Ctrl + C 退出,容器也就停止了,要再去启动容器
- --build:表示每次构建都重新执行一遍 Dockerfile 生成镜像(会重新安装 npm 包),不加的话如果镜像存在的话,就不会再执行 Dockerfile,一般是 Dockerfile 有变动时加上 --build
docker-compose 与 build 同目录
├─admin
└─build
└─index.html
└─Dockerfile
└─docker-compose.yml
则,docker-compose.yml
version: '3'
services:
admin:
build: ./
image: admin:v1
ports:
- 9001:80
container_name: admin
在 build 目录下运行
$ docker-compose up -d --build
字数超了,未完待续,部署篇~