乐趣区

关于前端:Docker-部署-React-全栈应用

前言

之前应用 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 来部署这几种状况也是比拟全面的例子了,能够给起初同学作为参考,内容比拟啰嗦,心愿能帮忙起初的同学少走一点坑,因为有些是本人的了解,可能会有谬误,还请大家斧正,互相学习。

我的项目地址

源码地址:https://github.com/Moon-Futur…

clone 下来参照目录哦~

一、React 篇

博客前台应用 Next.js 服务端渲染框架搭建,后盾治理界面应用 create-react-app 脚手架搭建,服务接口应用 Egg 框架(基于 Koa)。后盾治理和服务接口没什么好说的,就是一些 React 基础知识,这里次要说下 Next.js 中遇到的一些问题。

我的项目目录:

blog:前台界面,Next.js

admin:后盾治理界面,create-react-app 脚手架搭建

service:前后台服务接口

1. 获取渲染数据

因为是服务端渲染,所以页面初始数据会在服务器端获取后,渲染页面后返回给前端,这里有两个官网 API,getStaticPropsgetServerSideProps,从名字能够略微看出一点区别。(Next.js 9.3 版本以上,应用 getStaticPropsgetServerSideProps 来代替 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,https://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 装置最好是参照官网文档,避免出现版本更新问题。https://docs.docker.com/engine/install/ 英文吃力的,这两举荐一款神奇词典 欧陆词典,哪里不会点哪里,谁用谁说好。

Mac 和 Windows 都有客户端,能够很简略的下载安装,另外 Window 留神辨别专业版、企业版、教育版、家庭版

Window 专业版、企业版、教育版

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

有三种装置形式:

  1. Install using the repository
  2. Install from a package
  3. 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 镜像 https://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 镜像

前面有些操作会反复创立雷同的镜像,本来的镜像就会被笼罩变为 <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.

下面粗心是确定构建上下文后,两头的一些文件操作就只能在以后上下文之间进行,有两种形式解决

  1. 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
  2. 仍然将 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

还有很多参数,可自行理解 https://docs.docker.com/engine/reference/commandline/create/#options

生成容器后,咱们来看看

$ 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 装置

  1. 运行命令

    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
  2. 增加可执行权限

    sudo chmod +x /usr/local/bin/docker-compose
  3. 查看是否装置实现

    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

配置参数有很多 https://docs.docker.com/compose/compose-file/,官网能够详解,这里以及前面只说说用到的一些配置。

  • 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 里有雷同服务)

将 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

三、部署篇

下面简略介绍了 docker 的一些用法,借用动态 HTML 文件与 nignx 镜像创立运行了一个容器,然而其远远不止这些,上面就通过部署本博客来作为例子再探探外面的一些知识点。

源码地址:https://github.com/Moon-Futur…,可下载下来看着目录更清晰。

为了对立保护 docker 文件,以下将 docker 相干文件都放在各自目录下 docker 文件下,所以要特地主题构建上下文(context)的确定。

1. 部署前台 blog

端口映射 9000:9000,服务器端口: 容器端口,若是线上服务器,要先在平安组里开明对应的端口号

在 blog 目录下创立 docker 目录,docker 目录下创立三个文件

  • .dockerignore:拷贝文件疏忽列表
  • Dockefile
  • docker-compose.yml

.dockerignore

node_modules
.next

Dockefile

# node 镜像
# apline 版本的 node 会小很多
FROM node:12-alpine

# 在容器中创立目录
RUN mkdir -p /usr/src/app

# 指定工作空间,前面的指令都会在当前目录下执行
WORKDIR /usr/src/app

# 拷贝 package.json
COPY package.json /usr/src/app

# 装置依赖
RUN npm i --production --registry=https://registry.npm.taobao.org

# 拷贝其余所有文件到容器(除了 .dockerignore 中的目录和文件)COPY . /usr/src/app

# build
RUN npm run build

# 裸露端口 9000
EXPOSE 9000

# 运行容器时执行命令,每个 Dokcerfile 只能有一个 CMD 命令,多个的话只有最初一个会执行
CMD ["npm", "start"]

Docker 镜像是分层的,上面这些知识点十分重要:

  • Dockerfile 中的每个指令都会创立一个新的镜像层,每个 RUN 都是一个指令 https://docs.docker.com/engin…
  • 镜像层将被缓存和复用
  • 当 Dockerfile 的指令批改了,复制的文件变动了,或者构建镜像时指定的变量不同了,对应的镜像层缓存就会生效
  • 某一层的镜像缓存生效之后,它之后的镜像层缓存都会生效
  • 镜像层是不可变的,如果咱们再某一层中增加一个文件,而后在下一层中删除它,则镜像中仍然会蕴含该文件(只是这个文件在 Docker 容器中不可见了)。

所以咱们先拷贝 package.json,而后 RUN npm i 装置依赖,造成一个镜像层,再拷贝其余所有文件,造成一个镜像层,之后如果代码有所变动,然而 package.json 没有变动,再次执行时,就不会再装置依赖了,能够节俭很多工夫。package.json 有变动,才会从新执行 RUN run i 装置依赖。

如果生成了镜像 imageA,此时要删除 imageA,从新生成,记住学生成新的镜像 imageB,这样才会复用 npm 包,如果先删除了 imageA,再新生成 imageB,则又会重新安装依赖。

在 blog 目录下运行以下命令能够生成镜像 react_blog:blog

$ docker build -f docker/Dockerfile . -t react_blog:blog

第一次运行装置依赖时有点慢(有个 sharp 特地慢 …),刚开始我应用 node-sass 时,装置总是报错,起初索性就换成了 less,省心。如果想用 yarn 装置的话,这里 Dockerfile 里 npm 相干的命令也能够换成对于的 yarn 命令。

漫长的期待后终于 build 胜利,上面一些信息就是 npm run build 生成的文件

看看生成的镜像

$ docker images
REPOSITORY   TAG         IMAGE ID       CREATED         SIZE
react_blog   blog        fef06dfed97f   3 minutes ago   329MB
nginx        latest      ae2feff98a0c   31 hours ago    133MB
node         12-alpine   844f8bb6a3f8   3 weeks ago     89.7MB

而后来生成并运行容器

$ docker run -itd -p 9000:9000 --name react_blog_blog react_blog:blog

这里参数再阐明一下:

  • -i 参数让容器的规范输出继续关上,–interactive
  • -t 参数让 Docker 调配一个伪终端,并绑定到容器的规范输出上,–tty
  • -d 参数让容器在后盾,以守护过程的形式执行,–detach(Run container in background and print container ID)
  • --name 参数指定容器惟一名称,若不指定,则随机一个名称

-it 个别同时加上,-d 参数如果不加的话,运行容器胜利时,会进入一个终端命令界面,要想退出的话只能 Ctrl + C,退出之后容器也就退出了,docker ps -a 能够看到容器状态是 Exited (0),能够应用 docker start container 再次开启。加上 -d 的话容器就会间接在后盾运行,个别的话就加上 -d。大家能够试试,之后再删除容器就能够了。

以上容器运行胜利的话,在浏览器通过 服务器 ip:9000 就能够拜访到页面啦,Mac 或者 Windows 本地的话 localhost:9000 就能够拜访啦。

docker-compose.yml

version: '3'
services:
  web:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile
    image: react_blog:blog
    ports:
      - 9000:9000
    container_name: react_blog_blog

下面咱们通过 docker builddocker run 等命令学生成容器,再生成并运行容器,是不是有点繁琐,命令不好记,输出也麻烦,这里咱们就能够利用 docker-compose 来简化执行命令。

咱们看一下文件内容:

  • web:服务名
  • build:构建相干,前面执行 docker-compose 命令门路要和 docker-compose.yml 同一门路,所以这里 context 构建上下文抉择上一层源码目录,dockerfile 就是当前目录里的 Dockerfile
  • image:镜像名,如果有就间接应用,没有就通过下面的 Dockerfile 生成
  • ports:端口映射
  • container_name:容器名称,惟一。若不写,则为 当前目录_服务名_index,index 数字(从 1 累加),若这里为 docker_web_1

能够把下面用 Dockerfile 生成的容器删了 docker rm -f react_blog_blog,用 docker-compose up 生成试试

在 docker 目录下执行命令

$ docker-compose up -d

要想从新生成镜像能够 docker-compose up -d --build

以上便把 blog 前端页面部署好了,当初只是独自部署学习,前面会删了和后盾与接口一起部署。

2. 部署后盾 admin

端口映射 9001:9001,服务器端口: 容器端口,若是线上服务器,要先在平安组里开明对应的端口号

当初来独自部署 admin,在 Docker 篇时,咱们曾经应用到 admin 来简略部署学习制作镜像和生成容器,这里仍然先在 admin 目录下生成生成环境动态文件

$ npm run build

在 admin 下创立 docker 目录用来寄存 docker 相干文件,docker 目录下创立以下文件:

Dockerfile

FROM nginx

# 删除 Nginx 的默认配置
RUN rm /etc/nginx/conf.d/default.conf

EXPOSE 80

留神这里和下面的一些区别,

  • 这里把 nginx 默认的配置删除了,之后咱们本人配置一个
  • 没有 COPY 动态文件到容器,在 docker-compose.yml 通过挂在的形式实现

docker-compose.yml

version: '3'
services:
  admin:
    build: 
      context: ../
      dockerfile: ./docker/Dockerfile
    image: react_blog:admin
    ports: 
      - 9001:80
    volumes: 
      - ../build:/www
      - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
    container_name: react_blog_admin

这里多了 volumes(卷)项,参数是数组,对应 宿主机文件: 容器内文件

  • ../build:/www,build 内的文件挂在到容器 /www 目录下
  • ./nginx.conf:/etc/nginx/conf.d/nginx.conf,nginx.conf 挂载到容器 /etc/nginx/conf.d/nginx.conf 这个文件

这样做的益处是,当宿主机上的文件变动后,容器内的文件也会主动变动,相应的容器内文件变动,宿主机文件也会变动。这样之后源代码变动,从新打包生成 build 后,只须要放到服务器对应目录下,容器类 /www 下的类容就会是最新的,而不须要一次次的去执行 Dockerfile 拷贝 build 文件到容器内,数据库的数据通常也是这样保留在宿主机内,而避免容器删除时失落数据。

同理 nginx.conf 配置文件也是一样,不过改变 nginx 配置文件后,要重启以下容器才失效 docker restart container

来运行容器吧,在 docker 目录下执行命令

$ docker-compose up -d

查看容器是否运行胜利

$ docker ps -a
CONTAINER ID   IMAGE          COMMAND             CREATED         STATUS          PORTS            NAMES
7db8ce1c6814   react_blog:admin   "/docker-entrypoint.…"   16 minutes ago   Up 16 minutes   0.0.0.0:9001->80/tcp   react_blog_admin

运行失败的能够 docker logs container 查看日志

运行胜利的话,在浏览器通过 服务器 ip:9001 就能够拜访到页面啦,Mac 或者 Windows 本地的话 localhost:9001 就能够拜访啦。

nginx.conf

server {
  listen 80;
  sendfile on;
  sendfile_max_chunk 1M;
  tcp_nopush on;
  gzip_static on;

  location / {
    root /www;
    index index.html;
  }
}

root 记得和下面挂在目录雷同

3. 部署服务接口 service + Mysql

端口映射 9002:9002,服务器端口: 容器端口,若是线上服务器,要先在平安组里开明对应的端口号

当初咱们来部署服务接口,在 service 目录下创立 docker 目录,docker 目录下创立以下文件:

.dockerignore

node_modules
.github
article

article 目录用来寄存博客内容文件

Dockerfile

FROM node:alpine

# 配置环境变量
ENV NODE_ENV production

# 这个是容器中的文件目录
RUN mkdir -p /usr/src/app 

# 设置工作目录
WORKDIR /usr/src/app

# 拷贝 package.json 文件到工作目录
# !! 重要:package.json 须要独自增加。# Docker 在构建镜像的时候,是一层一层构建的,仅当这一层有变动时,从新构建对应的层。# 如果 package.json 和源代码一起增加到镜像,则每次批改源码都须要重新安装 npm 模块,这样木有必要。# 所以,正确的程序是: 增加 package.json;装置 npm 模块;增加源代码。COPY package.json /usr/src/app/package.json

# 装置 npm 依赖(应用淘宝的镜像源)
# 如果应用的境外服务器,无需应用淘宝的镜像源,即改为 `RUN npm i`。RUN npm i --production --registry=https://registry.npm.taobao.org

# 拷贝所有源代码到工作目
COPY . /usr/src/app

# 裸露容器端口
EXPOSE 9002

CMD npm start

docker-compose.yml

version: '3'
services:
  service:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile
    image: react_blog:service
    ports:
      - 9002:9002
    depends_on:
      - db
    environment:
      MYSQL_HOST: localhost
      MYSQL_USER: root
      MYSQL_PASSWORD: 8023
    volumes:
      - ../article:/usr/src/app/article
    container_name: react_blog_service
  db:
    image: mysql
    # volumes:
    #    - /db_data:/var/lib/mysql
    ports:
      - 33061:3306
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: 8023
      # MYSQL_USER: root
      MYSQL_PASSWORD: 8023
      MYSQL_DATABASE: react_blog
    container_name: react_blog_mysql

留神这里有运行了两个服务 service、db

service 服务是后端接口:

  • deponds_on:运行时会先运行 deponds_on 列表里的服务,避免依赖项还没运行,本人会报错
  • command:从 MySQL8.0 开始,默认的加密规定应用的是 caching_sha2_password,此命令能够更改加密规定。不加可能会报错 Client does not support authentication protocol requested by server; consider upgrading MySQL client
  • environment:环境变量,这里会传入代码中,在代码 /config/secret.js(secret-temp.js) 外面能够会应用到

    /**

*/

module.exports = {

// mysql 连贯配置
mysql: {
  host: process.env.MYSQL_HOST || 'localhost',
  port: process.env.MYSQL_PORT || '3306',
  user: process.env.MYSQL_USER || 'xxx',
  password: process.env.MYSQL_PASSWORD || 'xxx',
  database: process.env.MYSQL_DATABASE || 'xxxxxx',
},
// jwt
tokenConfig: {privateKey: 'xxxxxxxxxxxxxxxxxxxxxxxxx',},

}


- volumes:这里我把文章写入宿主机了,挂载到容器里

**db** 服务是 Mysql 数据库:- volumes:数据设置存储在宿主机

- ports:端口映射,宿主机通过 33061 端口能够拜访容器外部 Mysql,咱们之后就能够通过 Navicat 或其余数据库可视化工具来连贯
- environment:配置数据库
- MYSQL_ROOT_PASSWORD 必须要带上,设置 ROOT 账号的明码
- MYSQL_USER 容器登录 MySQL 用户名,** 留神 **,这里如果是 root 会报错 `ERROR 1396 (HY000): Operation CREATE USER failed for 'root'@'%' `,依据 https://github.com/docker-library/mysql/issues/129 可知,曾经存在一个 root 用户,无奈再创立,所以这个能够不带,就默认 root 用户登录,如果带的话就不要是 root,会新建一个账户
- MYSQL_PASSWORD 容器登录 Mysql 明码,对用户名 MYSQL_USER,如果是 ROOT,明码就是 MYSQL_ROOT_PASSWORD,如果是其余,就是设置新密码
- MYSQL_DATABASE 创立一个 react_blog 数据库,也能够不填,前面再进入容器或者 Navicat 创立,然而这里因为后端代码要连贯到 react_blog 数据库,不创立的会连贯会保留,所以还是加上。(切实不想加也能够后见创立好数据库后,才运行两个容器)在 service/docker 目录下执行命令

$ docker-compose up -d


运行胜利的话,看看 images 和 container

$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
react_blog service 89139d833458 About an hour ago 150MB
react_blog admin 1b5d6946f1fe 32 hours ago 133MB
react_blog blog fef06dfed97f 35 hours ago 329MB
nginx latest ae2feff98a0c 2 days ago 133MB
mysql latest ab2f358b8612 6 days ago 545MB
node 12-alpine 844f8bb6a3f8 3 weeks ago 89.7MB


能够看到多了 Mysql 和 react_blog:blog 镜像

$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5878940d7626 react_blog:blog “docker…” 5 seconds ago Up 4 seconds 0.0.0.0:9000->9000/tcp react_blog_blog
3bff0060de19 react_blog:admin “/docker…” 3 minutes ago Up 18 seconds 0.0.0.0:9001->80/tcp react_blog_admin
d8a899232e8c react_blog:service “docker…” About a Exited (1) 5 minutes ago react_blog_service
a9da07ff5cae mysql “docker…” About an hr 33060/tcp, 0.0.0.0:33061->3306/tcp react_blog_mysql


能够看到多了 react_blog_service 和 react_blog_mysql 容器,其中 react_blog_service 容器运行失败了,显示没事失败的先别快乐,咱们来看看日志

$ docker logs react_blog_service


errno: “ECONNREFUSED”
code: “ECONNREFUSED”
syscall: “connect”
address: “127.0.0.1”
port: 3306
fatal: true
name: “ECONNREFUSEDError”
pid: 47
hostname: d8a899232e8c


能够看出是数据库连贯失败了,在下面 **docker-compose.yml** 中咱们定义的环境变量 `MYSQL_HOST=localhost` 传给后端代码来连贯数据库,每个容器都相当一一个独立的个体,localhost 是 react_blog_service 本人的 ip (127.0.0.1),当然是拜访不到 react_blog_mysql,这个问题咱们在下一节再来解决,先来说说 Mysql。下面能够看到 Mysql 容器曾经胜利运行,咱们能够进入容器外部连贯 Mysql,还记得怎么进入容器吗

$ docker exec -it react_blog_mysql /bin/sh

$ ls
bin boot dev docker-entrypoint-initdb.d entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 8.0.22 MySQL Community Server – GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.

mysql>


能够看到顺利连贯 Mysql,输出 `exit` 能够退出容器。咱们也能够应用可视化工具来连贯,我这里应用 Navicat 来连贯

![image-20201218224818914](https://cloud-images-1255423800.cos.ap-guangzhou.myqcloud.com/Docker-03.png)

![image-20201218225257487](https://cloud-images-1255423800.cos.ap-guangzhou.myqcloud.com/Docker-04.png)



留神这里的端口 33061,下面咱们通过端口映射,通过宿主机端口 33061 能够拜访到 Mysql 容器内端口 3306,所以就连贯上啦。> 在这个过程中,我的服务器(宿主机)上的 Mysql 呈现了问题,连贯时报错 `2013 lost connection to mysql server at 'reading initial communication packet'`,我也不晓得是什么起因引起的,解决形式是运行命令 `systemctl start mysqld.service` 启动 Mysql 服务,也不知是哪里影响到了,不过前面我会间接连贯宿主机 Mysql,不应用容器,这样能够和其余我的项目对立治理数据,我工作比拟不便,且数据也较平安。### 4. 容器互联

下面留了一个问题,service 连贯数据库失败,当初咱们来尝试解决。参考 [Docker 筑梦师系列(一):实现容器互联](https://tuture.co/2020/01/12/cd44c84/)

#### 4.1 Network 类型

Network,顾名思义就是“网络”,可能让不同的容器之间互相通信。首先有必要要列举一下 Docker Network 的五种驱动模式(driver):- `bridge`:默认的驱动模式,即“网桥”,通常用于 ** 单机 **(更精确地说,是单个 Docker 守护过程)- `overlay`:Overlay 网络可能连贯多个 Docker 守护过程,通常用于 ** 集群 **,后续讲 Docker Swarm 的文章会重点解说
- `host`:间接应用主机(也就是运行 Docker 的机器)网络,仅实用于 Docker 17.06+ 的集群服务
- `macvlan`:Macvlan 网络通过为每个容器调配一个 MAC 地址,使其可能被显示为一台物理设施,实用于心愿直连到物理网络的应用程序(例如嵌入式零碎、物联网等等)- `none`:禁用此容器的所有网络

默认状况下,创立的容器都在 bridge 网络下,如下如所示,各个容器通过 dokcer0 可连贯到宿主机 HOST,并且各自调配到 IP,这种状况下,容器间相互拜访须要输出对方的 IP 地址去连贯。![image-20201220153944034](https://cloud-images-1255423800.cos.ap-guangzhou.myqcloud.com/Docker-05.png)



查看 network 列表

$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a75e040b03ed bridge bridge local
13545e6a3970 docker_default bridge local
5ec462838a1c host host local
c726e6887f10 none null local


这里有 4 的 network,默认原本只有 3 个,没有 docker_default,我也是写到这里才发现创立了一个 docker_default 网络,查找官网([Networking in Compose](https://docs.docker.com/compose/networking/#:~:text=By%20default%20Compose%20sets%20up,identical%20to%20the%20container%20name.))才发现,通过 docker-compose 来生成运行容器时,如果没指定 network,会主动创立一个 network,蕴含以后 docker-compose.yml 下的所有容器,network 名字默认为 **` 目录_default`**,这里目录就是 `docker` 恰好咱们这个几个 docker-compose.yml 都是放在 docker 目录下,所以创立的几个容器都是在  docker_default 网络里。能够一下命令查看网络详细信息

$ docker network inspect docker_default

[

{
    "Name": "docker_default",
    "Id": "13545e6a39708344b363b7fc16eefeb6775c37773222804ebd5b5fb6f28c38bb",
    "Created": "2020-12-16T11:03:37.2152073+08:00",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
        "Driver": "default",
        "Options": null,
        "Config": [
            {
                "Subnet": "172.24.0.0/16",
                "Gateway": "172.24.0.1"
            }
        ]
    },
    "Internal": false,
    "Attachable": true,
    "Ingress": false,
    "ConfigFrom": {"Network": ""},"ConfigOnly": false,"Containers": {"23891d43187e046eea25936dc0ab703964cc6c7213bb150ae9529da3e2e57662": {"Name":"react_blog_mysql","EndpointID":"649857f928e0444500cfd296035869678bf26162d429a4499b262776b2a1d264","MacAddress":"02:42:ac:18:00:03","IPv4Address":"172.24.0.3/16","IPv6Address":""},
        "3bff0060de19fc973039c07c1931e2c1efe30c6707bcd77d2ff7ea4dc01aaf63": {
            "Name": "react_blog_admin",
            "EndpointID": "25d8fa518b0ce27498f562372c3424aee174cb1d8fbf9f2445f1c6af8e6aab7f",
            "MacAddress": "02:42:ac:18:00:02",
            "IPv4Address": "172.24.0.2/16",
            "IPv6Address": ""},"5878940d7626a9fb20622cde4002075e390e5036036bafb99d80454d6cba594b": {"Name":"react_blog_blog","EndpointID":"a3f8ee36eda09f524be7ea16a67a1e13e62cf558e5480218bb523f877d478e4a","MacAddress":"02:42:ac:18:00:04","IPv4Address":"172.24.0.4/16","IPv6Address":""}
    },
    "Options": {},
    "Labels": {
        "com.docker.compose.network": "default",
        "com.docker.compose.project": "docker",
        "com.docker.compose.version": "1.25.1"
    }
}

]


能够看到 docker_default 网关地址为 `172.24.0.1`,其余几个容器 IP 别离为 `172.24.0.3`,`172.24.0.2`,`172.24.0.4`,所以这里的状况是这样的

![image-20201220154038077](https://cloud-images-1255423800.cos.ap-guangzhou.myqcloud.com/Docker-06.png)

下面说了默认网络 bridge 下容器见拜访只能输出 **IP 地址 ** 来连贯,而自定义的网络还能够通过 ** 容器名 ** 来连贯

> On user-defined networks like `alpine-net`, containers can not only communicate by IP address, but can also resolve a container name to an IP address. This capability is called **automatic service discovery**. 
>
> [Networking with standalone containers](https://docs.docker.com/network/network-tutorial-standalone/)

这就能够防止每次生成容器 IP 会变的问题了。晓得了这些,咱们在 service 接口里就可已通过 react_blog_mysql 来连贯 react_blog_mysql 容器了,service/docker/docker-compose.yml 批改如下:

version: ‘3’
services:
service:

build:
  context: ../
  dockerfile: ./docker/Dockerfile
image: react_blog:service
ports:
  - 9002:9002
restart: on-failure
depends_on:
  - db
environment:
  MYSQL_HOST: react_blog_mysql # 此处 localhost 换为 mysql 容器名,在同一个自定义网络下,变会主动解析为 IP 连贯
  MYSQL_USER: root
  MYSQL_PASSWORD: 8023
volumes:
  - ./article:/usr/src/app/article
container_name: react_blog_service

db:

image: mysql
ports:
  - 33061:3306
restart: on-failure
command: --default-authentication-plugin=mysql_native_password
environment:
  MYSQL_ROOT_PASSWORD: 8023
  MYSQL_PASSWORD: 8023
  MYSQL_DATABASE: react_blog
container_name: react_blog_mysql

在此运行命令

$ docker-compose up -d –build


![image-20201219011335466](https://cloud-images-1255423800.cos.ap-guangzhou.myqcloud.com/Docker-07.png)

能够看到服务容器已失常运行,`docker logs react_blog_service` 查看日志也没有报错,阐明曾经连接数数据库,在代码你我加了一个 get 测试接口,在浏览器输出 `IP:9002/api/test/get` 或者 `localhost:9002/api/test/get`,会返回一个 json 对象

{“message”:”Hello You Got It”}


** 这里我试了 N 久,始终有问题,**

- Mysql 创立失败,environment 我加了一个 MYSQL_USER: root,后果始终报错  `ERROR 1396 (HY000): Operation CREATE USER failed for 'root'@'%' `,依据 https://github.com/docker-library/mysql/issues/129 可知,曾经存在一个 root 用户,无奈再创立,所以这个能够不带,就默认 root 用户登录,如果带的话就不要是 root,会新建一个账户。这里间接去掉 MYSQL_USER,应用 root 登录

- service 创立失败,日志报错没连贯上 Mysql,我试试了良久,最初发现重启一下 service `docker start react_blog_service` 就能够了,所以我感觉应该是 Mysql 创立好后,数据口等一些配置还没搞好,所以 service 还连贯不上,就始终报错,等一会从新运行 service 就好了,所以这里加上了 restart 参数,报错就重新启动,这样就不必本人去重启了,等一会,看日志没问题,就是连贯胜利了。明明应用了 depends_on,为什么还会有这种问题呢,我也不太分明,不过官网有这段示例:

version: “3.9”
services:

web:
  build: .
  depends_on:
    - db
    - redis
redis:
  image: redis
db:
  image: postgres

> `depends_on` does not wait for `db` and `redis` to be“ready”before starting `web` - only until they have been started. If you need to wait for a service to be ready, see [Controlling startup order](https://docs.docker.com/compose/startup-order/) for more on this problem and strategies for solving it.
>
> Depends_on 在启动 Web 之前不会期待 db 和 Redis 处于“就绪”状态 - 仅在它们启动之前。>
> 应该就这个起因了~



咱们再来看看 docker_default 网络

$ docker network inspect docker_default

[

{
    "Name": "docker_default",
    "Id": "13545e6a39708344b363b7fc16eefeb6775c37773222804ebd5b5fb6f28c38bb",
    "Created": "2020-12-16T11:03:37.2152073+08:00",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
        "Driver": "default",
        "Options": null,
        "Config": [
            {
                "Subnet": "172.24.0.0/16",
                "Gateway": "172.24.0.1"
            }
        ]
    },
    "Internal": false,
    "Attachable": true,
    "Ingress": false,
    "ConfigFrom": {"Network": ""},"ConfigOnly": false,"Containers": {"3bff0060de19fc973039c07c1931e2c1efe30c6707bcd77d2ff7ea4dc01aaf63": {"Name":"react_blog_admin","EndpointID":"25d8fa518b0ce27498f562372c3424aee174cb1d8fbf9f2445f1c6af8e6aab7f","MacAddress":"02:42:ac:18:00:02","IPv4Address":"172.24.0.2/16","IPv6Address":""},
        "5878940d7626a9fb20622cde4002075e390e5036036bafb99d80454d6cba594b": {
            "Name": "react_blog_blog",
            "EndpointID": "a3f8ee36eda09f524be7ea16a67a1e13e62cf558e5480218bb523f877d478e4a",
            "MacAddress": "02:42:ac:18:00:04",
            "IPv4Address": "172.24.0.4/16",
            "IPv6Address": ""},"83005eec8d50071a6c23a2be4af8552983c09c532e937f04d79f02f8eb68acc9": {"Name":"react_blog_mysql","EndpointID":"265ed7793c98287a05ccf8997e81671287a02ee8ea464984996083a34abe10dd","MacAddress":"02:42:ac:18:00:03","IPv4Address":"172.24.0.3/16","IPv6Address":""},
        "937339a37ce726e704ec21b31b4028a97967a00de01438557e5a60d8538a51c8": {
            "Name": "react_blog_service",
            "EndpointID": "934d26f32a2b23e2cb4691020cb93d26c97b9647108047b492c3f7dd2be6faef",
            "MacAddress": "02:42:ac:18:00:05",
            "IPv4Address": "172.24.0.5/16",
            "IPv6Address": ""
        }
    },
    "Options": {},
    "Labels": {
        "com.docker.compose.network": "default",
        "com.docker.compose.project": "docker",
        "com.docker.compose.version": "1.25.1"
    }
}

]


能够看到 react_blog_service 也已失常退出网络,IP 为 172.24.0.5



#### 4.2 自定义 Network

docker_default 网络是依据目录来创立的,凑巧咱们这几个我的项目 docker-compose.yml 文件都放在 docker 目录下,所以都在一个网络,如果名称变了就不在一个网络,并且之后我的项目可能还会有 docker 目录,全副都在一个网络也是不太好的,所以这里咱们来自定义本次我的项目的网络。***blog/docker/docker-compose.yml***

version: ‘3’
services:
blog:

build:
  context: ../
  dockerfile: ./docker/Dockerfile
image: react_blog:blog
ports:
  - 9000:9000
networks: 
  - react_blog
container_name: react_blog_blog

networks:
react_blog:


***admin/docker/docker-compose.yml***

version: ‘3’
services:
admin:

build:
  context: ../
  dockerfile: ./docker/Dockerfile
image: react_blog:admin
ports:
  - 9001:80
volumes:
  - ../build:/www
  - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
networks:
  - react_blog
container_name: react_blog_admin

networks:
react_blog:


***service/docker/docker-compose.yml***

version: ‘3’
services:
service:

build:
  context: ../
  dockerfile: ./docker/Dockerfile
image: react_blog:service
ports:
  - 9002:9002
depends_on:
  - db
environment:
  - MYSQL_HOST=react_blog_mysql # 此处 localhost 换为 mysql 容器名,在同一个自定义网络下,变会主动解析为 IP 连贯
  - MYSQL_USER=root
  - MYSQL_PASSWORD=8023
volumes:
  - ./article:/usr/src/app/article
networks:
  - react_blog
container_name: react_blog_service

db:

image: mysql
ports:
  - 33061:3306
command: --default-authentication-plugin=mysql_native_password
environment:
  - MYSQL_ROOT_PASSWORD=8023
  - MYSQL_USER=root
  - MYSQL_PASSWORD=8023
  - MYSQL_DATABASE=react_blog
networks:
  - react_blog
container_name: react_blog_mysql

networks:
react_blog:


- 与 services 同级的 networks:创立一个新的 network,这里生成的 network 最终名称也会加上目录名,docker_react_blog。- 服务外部的 networks:退出哪些网络,参数带“-”阐明是数组,能够退出多个网络,这里咱们全副退出 react_blog,不分前后端了

** 留神:**

这样在 dockor-compose.yml 里生成的 network 都会加上当前目录名,若想不带,能够本人学生成一个

$ docker network create my_net


而后在 dockor-compose.yml 里

version: ‘3’
services:
service:

build:
  context: ../
  dockerfile: ./docker/Dockerfile
image: react_blog:service
ports:
  - 9002:9002
depends_on:
  - db
environment:
  - MYSQL_HOST=react_blog_mysql # 此处 localhost 换为 mysql 容器名,在同一个自定义网络下,变会主动解析为 IP 连贯
  - MYSQL_USER=root
  - MYSQL_PASSWORD=8023
volumes:
  - ./article:/usr/src/app/article
networks:
  - my_net
container_name: react_blog_service

db:

image: mysql
ports:
  - 33061:3306
command: --default-authentication-plugin=mysql_native_password
environment:
  - MYSQL_ROOT_PASSWORD=8023
  - MYSQL_USER=root
  - MYSQL_PASSWORD=8023
  - MYSQL_DATABASE=react_blog
networks:
  - my_net
container_name: react_blog_mysql

networks:
my_net:

  external: true

加个 external 参数则应用曾经创立的 network(my_net),不会再去创立或加上目录名。咱们再来从新创立容器,先删除全副容器

$ docker stop $(docker ps -aq)
$ docker rm $(docker ps -aq)


在进入各个目录别离执行 `docker-compose up -d`,在运行第一个时会看到 `Creating network "docker_react_blog" with the default driver` 这句话,阐明创立了一个新的 network,咱们来看看

$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a75e040b03ed bridge bridge local
13545e6a3970 docker_default bridge local
e1ceb437a4fd docker_react_blog bridge local
5ec462838a1c host host local
c726e6887f10 none null local

$ docker network inspect docker_react_blog
[

{
    "Name": "docker_react_blog",
    "Id": "e1ceb437a4fdc5de91e51ff8831e21b565c92754159ad7057de36758e548a92f",
    "Created": "2020-12-19T01:39:02.201644444+08:00",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
        "Driver": "default",
        "Options": null,
        "Config": [
            {
                "Subnet": "172.18.0.0/16",
                "Gateway": "172.18.0.1"
            }
        ]
    },
    "Internal": false,
    "Attachable": true,
    "Ingress": false,
    "ConfigFrom": {"Network": ""},"ConfigOnly": false,"Containers": {"00da404f6f050b9b2f20e39bbb136fef614e8dfee85ec31bd6000bfd59cc2dab": {"Name":"react_blog_mysql","EndpointID":"1cb966cc731eca3e9721e6d3edcfcac6152b66051faa934557f567e9e36c75c6","MacAddress":"02:42:ac:12:00:04","IPv4Address":"172.18.0.4/16","IPv6Address":""},
        "ad1480e48e8e7ed160b1d4bcf7eed77d74505aea7581d48d8931206772b5d805": {
            "Name": "react_blog_service",
            "EndpointID": "8866c3457382d6baa945da09aef40da54c7dfdea0f393485001c35bb37d201a0",
            "MacAddress": "02:42:ac:12:00:05",
            "IPv4Address": "172.18.0.5/16",
            "IPv6Address": ""},"b518d40b5021d3fdec7b7e62fbaa47b8a705a38346ccba2b9814174e46b67cd0": {"Name":"react_blog_admin","EndpointID":"9a58ff20dc57d4d1fa6af83482051a68e80e22a5e37cf8e0cb3570b78102f107","MacAddress":"02:42:ac:12:00:03","IPv4Address":"172.18.0.3/16","IPv6Address":""},
        "db0050257a8e8a0fa430ea04b009ae819dbf04ef001cf1027ec2b5565403b48e": {
            "Name": "react_blog_blog",
            "EndpointID": "664794ed292871bc7fd8e1c4eaa56f682a6be5d653209f84158f3334a4f30660",
            "MacAddress": "02:42:ac:12:00:02",
            "IPv4Address": "172.18.0.2/16",
            "IPv6Address": ""
        }
    },
    "Options": {},
    "Labels": {
        "com.docker.compose.network": "react_blog",
        "com.docker.compose.project": "docker",
        "com.docker.compose.version": "1.25.1"
    }
}

]




#### 4.3 调用接口

当初还有一个问题,咱们在代码中调用接口模式是 `http://localhost:9002/api/xxx`,在 react_blog_blog 容器中调用接口 localhost 是自身本人,没有调到 react_blog_service 外面的接口。** 针对 admin**

在代码中,咱们这样来调接口

const HOST = process.env.NODE_ENV === ‘development’ ? ‘http://localhost:9002’ : ”

const API = {
getArticleList: HOST + ‘/api/getArticleList’,
getArticle: HOST + ‘/api/getArticle’,
addArticle: HOST + ‘/api/addArticle’,
delArticle: HOST + ‘/api/delArticle’,

getTagList: HOST + ‘/api/getTagList’,
addTag: HOST + ‘/api/addTag’,
delTag: HOST + ‘/api/delTag’,

register: HOST + ‘/api/register’,
login: HOST + ‘/api/login’,
}

export default API


![image-20201219160058561](https://cloud-images-1255423800.cos.ap-guangzhou.myqcloud.com/Docker-08.png)



会发现接口 404,咱们通过 nginx 来代理接口申请

***admin/docker/nginx.conf***

server {
listen 80;
sendfile on;
sendfile_max_chunk 1M;
tcp_nopush on;
gzip_static on;

location /api {

proxy_pass http://react_blog_service:9002;

}

location / {

root /www;
index index.html;

}
}


以 /api 为结尾的申请,咱们都转发到 react_blog_service 容器 9002 端口,将 nginx.conf 拖到服务器,因为咱们是将此文件挂载到容器外部的,所以这里只须要重启一下容器

$ docker restart react_blog_admin


再看看申请接口,能够看到申请 200 胜利,返回数据,如果返回 500,阐明数据库还没建表,将目录下 react_blog.sql 导入数据库就能够了。![image-20201219160458357](https://cloud-images-1255423800.cos.ap-guangzhou.myqcloud.com/Docker-09.png)



** 针对 blog**

开始我认为通过环境变量(Next 中要存储在运行时变量里 [Runtime Configuration](https://nextjs.org/docs/api-reference/next.config.js/runtime-configuration))来传递申请 HOST (react_blog_service || localhost),但发现 react_blog_service 间接拼在前端接口里拜访是不可行的(getServerSideProps 可行),所以最初还是改为 nginx 来代理申请,并且前面咱们必定还是要通过域名来拜访网站的,所以还是须要 nginx,那么咱们就为前台页面来加一个 nginx 容器。**1、创立环境变量 **

***blog/docker/Dockerfile***

node 镜像

apline 版本的 node 会小很多

FROM node:12-alpine

在容器中创立目录

RUN mkdir -p /usr/src/app

指定工作空间,前面的指令都会在当前目录下执行

WORKDIR /usr/src/app

拷贝 package.json

COPY package.json /usr/src/app

装置依赖

RUN npm i –production –registry=https://registry.npm.taobao.org

拷贝其余所有文件到容器(除了 .dockerignore 中的目录和文件)

COPY . /usr/src/app

build 阶段获取

ENV HOST react_blog_service ## 减少一个环境变量,在 build 阶段可获取到,肯定放在 npm run build 前一行

build

RUN npm run build

裸露端口 9000

EXPOSE 9000

运行容器时执行命令,每个 Dokcerfile 只能有一个 CMD 命令,多个的话只有最初一个会执行

CMD [“npm”, “start”]


代码中,设置运行是变量 ***blog/next.config.js***

const withCSS = require(‘@zeit/next-css’)
const withLess = require(‘@zeit/next-less’)
module.exports = () =>
withLess({

...withCSS(),
// 改为 nginx 代理
publicRuntimeConfig: {HOST: process.env.HOST || 'localhost', // 如果是 docker build,此处 process.env.HOST,否则就 localhsot,不影响本地运行},

})


在 ***blog/config/api***

import getConfig from ‘next/config’
const {publicRuntimeConfig} = getConfig()

const SSRHOST = http://${publicRuntimeConfig.HOST}:9002
const HOST = http://localhost:9002

export const SSRAPI = {
getArticleList: SSRHOST + ‘/api/getArticleList’,
getArticle: SSRHOST + ‘/api/getArticle’,
}

export const API = {
getArticleList: HOST + ‘/api/getArticleList’,
getArticle: HOST + ‘/api/getArticle’,
}


这里有点麻烦,我不晓得我的了解对不对,但试了多种状况只有这种本地和 docker 部署才都能够。- 如果是本地运行(不应用 docker),服务端获取数据(**getServerSideProps**)和页面中获取数据间接应用服务接口地址(localhost:9002)即可

- 如果是 docker 运行,服务端获取数据(**getServerSideProps**)须要间接带上服务接口容器地址,无奈通过 nginx 代理,页面中获取数据调用接口则职能通过 nginx 代理的形式

**2、nginx 代理 **

批改 ***blog/docker/docker-compose.yml***,减少一个 nginx 容器

version: ‘3’
services:
blog:

build:
  context: ../
  dockerfile: ./docker/Dockerfile
image: react_blog:blog
# ports:
#   - 9000:9000
networks:
  - react_blog
container_name: react_blog_blog

nginx:

build:
  context: ../
  dockerfile: ./docker/Dockerfile-nginx
image: react_blog:nginx
ports:
  - 9000:80
volumes:
  - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
networks:
  - react_blog
container_name: react_blog_nginx

networks:
react_blog:


***blog/docker/Dockerfile-nginx***

FROM nginx

删除 Nginx 的默认配置

RUN rm /etc/nginx/conf.d/default.conf

EXPOSE 80


***blog/docker/nginx.conf***

server {
listen 80;
sendfile on;
sendfile_max_chunk 1M;
tcp_nopush on;
gzip_static on;

location /api {

proxy_pass http://react_blog_service:9002;

}

location / {

proxy_pass http://react_blog_blog:9000;

}
}


**3、生成容器 **

因为 blog 的内容有变,所以须要从新生成镜像,应用 `docker-compose up -d --build` 会从新下载 npm node_modules,比较慢,所以还是学生成镜像。在 blog 目录下执行

$ docker build -f docker/Dockerfile . -t react_blog:blog


在 blog/docker 下执行

$ docker-compose up -d


运行胜利的话,再试试接口就能够获取数据啦。### 5. 连贯宿主机 Mysql

下面遇到一个问题,在下面过程中,我的服务器(宿主机)上的 Mysql 呈现了问题,连贯时报错 `2013 lost connection to mysql server at 'reading initial communication packet'`,我也不晓得是什么起因引起的,解决形式是运行命令 `systemctl start mysqld.service` 启动 Mysql 服务,也不知是哪理影响到了。因为之前其余我的项目都是独自部署的,没应用 docker,数据都在宿主机 Mysql 上,所以我还是跟偏向于对立治理,自适应宿主机一个 Mysql,上面来看看怎么实现吧。这里有两种形式

** 形式一:network_mode: host**

批改 ***service/docker/docker-compose.yml***

version: ‘3’
services:
service:

build:
  context: ../
  dockerfile: ./docker/Dockerfile
image: react_blog:service
ports:
  - 9002:9002
restart: on-failure
# depends_on:
#   - db
environment:
  # MYSQL_HOST: react_blog_mysql # 此处 localhost 换为 mysql 容器名,在同一个自定义网络下,变会主动解析为 IP 连贯
  MYSQL_USER: root
  MYSQL_PASSWORD: 8023
volumes:
  - ../article:/usr/src/app/article
network_mode: host
# networks:
#   - react_blog
container_name: react_blog_service

# db:
# image: mysql
# ports:
# – 33061:3306
# restart: on-failure
# command: –default-authentication-plugin=mysql_native_password
# environment:
# MYSQL_ROOT_PASSWORD: 8023
# MYSQL_PASSWORD: 8023
# MYSQL_DATABASE: react_blog
# networks:
# – react_blog
# container_name: react_blog_mysql
networks:
react_blog:


service/docker 下执行命令

$ docker-compose up -d

$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
af9e525e7d14 react_blog:service “docker-entrypoint.s…” 28 seconds ago Up 26 seconds react_blog_service


能够看到 service 运行失常,且没有端口映射,`docker inspect react_blog_service` 也没有调配 IP,这种就相当于一个 Node 利用本人连贯到宿主机 Mysql。然而对于页面接口申请来说,因为 react_blog_service 已不在 docker_react_blog,所以就要应用宿主机 IP 地址来拜访了。***nginx.conf***

server {
listen 80;
sendfile on;
sendfile_max_chunk 1M;
tcp_nopush on;
gzip_static on;

location /api {

# proxy_pass http://react_blog_service:9002;
proxy_pass http://xxx.xx.xxx.x:9002; # xxx.xx.xxx.x 为宿主机(服务器)IP 

}

location / {

proxy_pass http://react_blog_blog:9000;

}
}


服务端渲染接口也是一样

node 镜像

apline 版本的 node 会小很多

FROM node:12-alpine

在容器中创立目录

RUN mkdir -p /usr/src/app

指定工作空间,前面的指令都会在当前目录下执行

WORKDIR /usr/src/app

拷贝 package.json

COPY package.json /usr/src/app

装置依赖

RUN npm i –production –registry=https://registry.npm.taobao.org

拷贝其余所有文件到容器(除了 .dockerignore 中的目录和文件)

COPY . /usr/src/app

build 阶段获取,xxx.xx.xxx.x 为宿主机(服务器)IP

ENV HOST xxx.xx.xxx.x

build

RUN npm run build

裸露端口 9000

EXPOSE 9000

运行容器时执行命令,每个 Dokcerfile 只能有一个 CMD 命令,多个的话只有最初一个会执行

CMD [“npm”, “start”]


这种形式是不是很麻烦,还要裸露服务器 IP 地址,所以我抉择形式二



** 形式二:**

批改 ***service/docker/docker-compose.yml***

version: ‘3’
services:
service:

build:
  context: ../
  dockerfile: ./docker/Dockerfile
image: react_blog:service
ports:
  - 9002:9002
restart: on-failure
# depends_on:
#   - db
environment:
  MYSQL_HOST: 172.17.0.1
  MYSQL_USER: root
  MYSQL_PASSWORD: 8023
volumes:
  - ../article:/usr/src/app/article
networks:
  - react_blog
container_name: react_blog_service

# db:
# image: mysql
# ports:
# – 33061:3306
# restart: on-failure
# command: –default-authentication-plugin=mysql_native_password
# environment:
# MYSQL_ROOT_PASSWORD: 8023
# MYSQL_PASSWORD: 8023
# MYSQL_DATABASE: react_blog
# networks:
# – react_blog
# container_name: react_blog_mysql
networks:
react_blog:


这里 MYSQL_HOST 为 172.17.0.1,下面也说了,容器能够通过此 IP 来连贯到宿主机,所以这就连贯上宿主机的 Mysql 了,其余的中央就不须要改了。### 6. 一个 docker-compoer.yml

后面用了 3 个 docker-compose.yml 来启动各自的我的项目,还是挺繁琐的,咱们来写一个汇总的,一个命令运行所以,当然前面某一个我的项目须要从新跑,也能够进入各自目录去运行本人的 docker-compose.yml

在我的项目根目录创立 ***docker/docker-compose.yml***,创立 docker 目录,是为了创立的 network 和单个我的项目运行是创立的统一

version: ‘3’
services:
blog:

build:
  context: ../blog
  dockerfile: ./docker/Dockerfile
image: react_blog:blog
networks:
  - react_blog
container_name: react_blog_blog

nginx:

build:
  context: ../blog
  dockerfile: ./docker/Dockerfile-nginx
image: react_blog:nginx
ports:
  - 9000:80
volumes:
  - ../blog/docker/nginx.conf:/etc/nginx/conf.d/nginx.conf
networks:
  - react_blog
container_name: react_blog_nginx

admin:

build:
  context: ../admin
  dockerfile: ./docker/Dockerfile
image: react_blog:admin
ports:
  - 9001:80
volumes:
  - ../admin/build:/www
  - ../admin/docker/nginx.conf:/etc/nginx/conf.d/nginx.conf
networks:
  - react_blog
container_name: react_blog_admin

service:

build:
  context: ../service
  dockerfile: ./docker/Dockerfile
image: react_blog:service
ports:
  - 9002:9002
restart: on-failure
environment:
  MYSQL_HOST: 172.17.0.1
  MYSQL_USER: root
  MYSQL_PASSWORD: 8023
volumes:
  - ../service/article:/usr/src/app/article
networks:
  - react_blog
container_name: react_blog_service

networks:
react_blog:


进行并删除之前创立的所有容器

$ docker stop $(docker ps -aq)
$ docker rm $(docker ps -aq)


进入 /docker 目录执行,

$ docker-compose up -d
Building nginx
Step 1/3 : FROM nginx
—> ae2feff98a0c
Step 2/3 : RUN rm /etc/nginx/conf.d/default.conf
—> Running in bb163c42c6b5
Removing intermediate container bb163c42c6b5
—> 282cb303dddf
Step 3/3 : EXPOSE 80
—> Running in 9b77ebd39952
Removing intermediate container 9b77ebd39952
—> fbb18dda70af
Successfully built fbb18dda70af
Successfully tagged react_blog:nginx
WARNING: Image for service nginx was built because it did not already exist. To rebuild this image you must use docker-compose build or docker-compose up --build.
Building admin
Step 1/3 : FROM nginx
—> ae2feff98a0c
Step 2/3 : RUN rm /etc/nginx/conf.d/default.conf
—> Using cache
—> 282cb303dddf
Step 3/3 : EXPOSE 80
—> Using cache
—> fbb18dda70af
Successfully built fbb18dda70af
Successfully tagged react_blog:admin
WARNING: Image for service admin was built because it did not already exist. To rebuild this image you must use docker-compose build or docker-compose up --build.
Creating react_blog_admin … done
Creating react_blog_service … done
Creating react_blog_blog … done
Creating react_blog_nginx … done

$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1fbb15abdd30 react_blog:service “docker” 13 seconds ago Up 6 seconds 0.0.0.0:9002->9002/tcp react_blog_service
fbee53e25c3a react_blog:admin “/docker” 13 seconds ago Up 6 seconds 0.0.0.0:9001->80/tcp react_blog_admin
70cb25f87d14 react_blog:blog “docker” 13 seconds ago Up 6 seconds 9000/tcp react_blog_blog
aa9fbf2afea4 react_blog:nginx “/docker” 13 seconds ago Up 6 seconds 0.0.0.0:9000->80/tcp react_blog_nginx


运行胜利~



### 7. 域名

我当初是通过宿主机的 nginx 来代理域名拜访 IP:9000,而后拜访到 react_blog_nginx 容器,本想是间接在 react_blog_nginx 中做代理,然而试了没胜利。想了想,拜访 react_blog_nginx 是通过端口映射,宿主 IP:9000 拜访到的,如果在 react_blog_nginx 外部配置域名,总感觉是无法访问,这点还没想过,这几天再试试。## 结语

终于写完了,写之前曾经学习尝试了良久,认为很有把握了,后果在写的过程中又遇到一堆问题,一个问题可能都会卡良久天,各种百度,Google,油管都用上啦,总算解决了遇到的所有问题,当然这些问题可能只满足了我当初的部署需要,其中还有很多知识点,没有接触到,不过没关系,我就是想胜利部署前端我的项目就能够了。
退出移动版