Technology stack:

  • docker
  • node
  • pm2
  • shell
  • webhook

docker

Docker can create/destroy/manage multiple containers flexibly. You can build any environment you want in the container. After installing Docker, you can create as many containers as you want

The installation

Click Install Docker download completion, restart the computer, open the console and enter docker -v to check whether the installation is successful

Important concepts

Three concepts of Docker

  1. Mirror image (image)
  2. The container (the container)
  3. Warehouse (repository)

An image is a container template, and an image can create multiple containers, like the relationship between a class and an example in JS

There are two ways to obtain the image

  1. Create a Dockerfile file
  2. Use an image from dockerHub or another source

Dockerfile

Dockerfile is a docker image configuration file, define how to generate an image

Create a file

Create the file index.html

<! DOCTYPEhtml>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
        <title>Document</title>
    </head>
    <body>
        <h2>hello docker</h2>
    </body>
</html>
Copy the code

Create a Dockerfile file

FROM nginx
COPY index /usr/share/nginx/html/index.html
EXPOSE 80
Copy the code
  • FROM nginx: Based on the official Nginx image
  • COPY index/usr/share/nginx/HTML/index. HTML: index under the current folder. The HTML replacement container/usr/share/nginx/HTML/index. The HTML
  • EXPOSE 80: The container exposes port 80 (ports in the container that need to be defined when creating the container)

Create an image

Open the console in the current directory and run the following command to build the image:

docker build . -t test-image:latest
Copy the code
  • Build: Creates an image
  • . : Use the Dockerfile file in the current directory
  • -t: indicates that the mirroring version is tagged
  • Test-image: specifies the image name
  • : Latest: Specifies the tag version

Look at mirror

docker images
Copy the code

A REPOSITORY with the name test-image indicates that the image has been created successfully

Create a container

docker run -d -p 80:80 --name test-container test-image:latest
Copy the code
  • Run: Creates and runs the Docker container
  • -d: indicates the background running container
  • 80:80: Maps port 80 (before colon) of the current server to port 80 (after colon) of the container.
  • –name: Specifies the container name
  • Test-image :latest: indicates the name of the image to be used.

A browser that opens localhost displays the contents of index.html

Check the container

docker ps -a
Copy the code
  • Ps: View the Docker container (running)
  • -a: View all containers, including those that are not in the running state

dockerHub

Github is the repository for code, and dockerHub is the repository for images. Developers can upload images generated by Dockerfile to dockerHub to store custom images, or directly use official images.

docker pull nginx
docker run -d -p 81:80 --name nginx-container nginx
Copy the code

Port 81 is used to map to port 80 of the container. The browser should see Welcome Tonginx by visiting localhost:81.

Why docker

  • Environment unified

Docker solves a century-old problem: it is obviously good on my computer. Developers can upload the development environment to docker warehouse with docker image, and pull and run the same image in the production environment to keep the environment consistent. Sign up for a dockerHub account and log in

docker login    Local docker login dockerHub accountDocker build. -t your account /docker-image-test:0.1# build an image for uploading to dockerHub (prefix with account)Docker push your account /docker-image-test:0.1# submit an image named docker-image-test.
# open dockerHub to check whether the upload is successful
Copy the code

Let’s test the pull mirror

docker images -a    View the local image and copy the ID of the image you just builtDocker RMI replication ID# delete the image that was created locally
docker images -a    Check that the deletion is successfulDocker pull /docker-image-test:0.1# pull the docker-test-image image from the account
docker images -a    Confirm pull success
Copy the code
  • Easy to roll back

When creating an image, you can tag the current version and quickly roll back to the previous version if there is a problem with the environment of one version.

  • Environmental isolation

Cocker can be used to make the server cleaner and build environments can be kept in containers.

  • Efficient/resource saving

Unlike real servers and virtual machines, containers do not contain operating systems. Containers are created and destroyed efficiently.

Other Common Commands

  • Stop container: docker stop container ID
  • Start container: docker start Container ID
  • Delete container: docker rm container ID (must stop first)
  • View the container log: Docker logs container name
  • Enter container: Docker exec(enter container) it(assign a dummy terminal, keep STDIN open even without attaching) container name /bin/sh
  • Docker cp Container name: Container path Local path

Automated front-end deployment

You need to update your site content before using automated deployment

  • Git push commits code
  • Run NPM run build locally to generate the build
  • Upload the build artifacts to the server via FTP and other forms

Once automated deployment is implemented

  • Git push commits code
  • The server automatically updates the image
  • Automatically run NPM run Build in the image to generate the build
  • The server automatically creates containers

You can see that with automated deployment, developers only need to submit code to the repository, and the rest can be done through script automation on the server.

The server

You need to have a server first, and the following uses centos7 as an example.

Installation environment

  • docker

Local docker environment, the server also needs to install Docker

curl -sSL https://get.daocloud.io/docker | sh   # installation docker
systemctl start docker                          # start docker
Copy the code
  • git

Automated deployment involves pulling up to date code, so install a Git environment here

yum install git
Copy the code
  • node
curl -sL https://rpm.nodesource.com/setup_10.x | bash -  # set nodesource
yum -y install nodejs                                   # installation node
node -v
Copy the code
  • pm2

Scripts can be run in the background

npm i pm2 -g
Copy the code

Create a project

Creating a local project

npx create-react-app docker-test
Copy the code

Open Gitee (github also works, I’m using Gitee here), create a project and open the local project to the console

Git remote add Origin gitee project location git push -u origin masterCopy the code

When the console displays Branch ‘master’ set up to track remote Branch ‘master’ from ‘origin’. Refresh the browser to see that the remote repository has been created.

webhook

Open the remote repository and click Manage — WebHooks — add WebHooks

  • URL: Enter the server address. HTTP or HTTPS is required. The port number is 3000 for now
  • WebHook Password/signature key: this parameter is not required
  • Select event: Select the default Push. When a Push event occurs in the warehouse, Gitee will automatically send a POST request to the URL we filled in.
  • Enable: Enable Webhook

Process project update requests

When the server receives a POST request sent after a project update, an image needs to be created/updated for automated deployment.

Create Dockerfile

Open the local project and create a new Dockerfile to build the image

# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY . .
RUN npm i
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-state
COPY --from=build-stage /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx"."-g"."daemon off;"]
Copy the code

Build stage

  • FROM node:lts-alpine as build-stage: use node image, version lTS-Alpine (smaller than latest version, more suitable as docker image)
  • WORKDIR /app: Set the workspace to /app, isolated from other file systems
  • COPY.. : copies files to the container’s /app directory
  • RUN NPM I: Open the console and RUN NPM I
  • RUN NPM RUN build: Open the console and RUN NPM RUN build

Production Stage Deployment stage

  • FROM nginx:stable-alpine as production-stage: Use the nginx image of stable-alpine and name the stage production-stage
  • COPY –from=build-stage /app/build /usr/share/nginx/ HTML: –from=build-stage Can read files from build-stage, so the current command is copied from /app/build in build-stage to /usr/share/nginx/html in build-stage
  • EXPOSE 80: The container exposes port 80 to the public
  • CMD [“nginx”, “-g”, “daemon off;”] After the container is created, run the nginx -g daemon off command to keep nginx running in the foreground

Open console in local project

# copy Dockerfile from current folder to remote server. My login account is root. My server address is 47.103.74.196SCP. / Dockerfile [email protected]: / root# Are you sure you want to continue connecting (yes/no)? yes
# [email protected]'s password: Enter the connection password.
Copy the code

Create dockerignore.

Similar to.gitignore,.dockerignore can ignore certain files when creating mirror replicas.

Create a. Dockerignore file in your local project

# .dockerignore
node_modules
Copy the code

Copy.dockerignore to the server

SCP. /. Dockerignore [email protected]: / rootCopy the code

Creating an HTTP Server

You may have discovered that the test request sent by the remote repository has timed out, so you need to create an HTTP service to handle the POST request sent by webhook (this port needs to be enabled in the security group). Ecs.console.aliyun.com/#/securityG…). The local project creates the file index.js

const http = require('http')

http.createServer(async (req, res) => {
    if (req.method === 'POST' && req.url === '/') {
        / /...
    }
    res.end('success')
}).listen(3000.() = > {
    console.log('server is ready')})Copy the code

Pull warehouse code

The repository triggers a push event, and the server receives a POST request to pull the latest code changes to index.js

const http = require('http')
const path = require('path')
const fs = require('fs')
const { execSync } = require('child_process')

const repository = 'docker-test'

const deleteFolderRecursive = path= > {
    if (fs.existsSync(path)) {  // The path exists
        fs.readdirSync(path).forEach(v= > { // Read the files in this path
            const curPath = path + '/' + v
            if (fs.statSync(curPath).isDirectory()) {
                deleteFolderRecursive(curPath)  // If it is a folder, delete it recursively
            } else {
                fs.unlinkSync(curPath)  // Delete the file directly
            }
        })
        fs.rmdirSync(path)
    }
}

http.createServer(async (req, res) => {
    let projectDir = ' '
    if (req.method === 'POST' && req.url === '/') {
        console.log('Request received', req.url)
        projectDir = path.resolve(`. /${repository}`)    // Define the project directory
        deleteFolderRecursive(projectDir)       // Delete the cloned item recursively
        execSync(`git clone https://gitee.com/zhangfangbiao/${repository}.git`, {   // Start cloning
            stdio: 'inherit'
        })
    }
    res.end(projectDir)
}).listen(3000.() = > {
    console.log('server is ready')})Copy the code

Create images and containers

Now that the code has been pulled successfully, all you need to do is create an image using Dockerfile and update the container. Modified index. Js

const http = require('http')
const path = require('path')
const fs = require('fs')
const { execSync } = require('child_process')

const repository = 'docker-test'

const deleteFolderRecursive = path= > {
    if (fs.existsSync(path)) {  // The path exists
        fs.readdirSync(path).forEach(v= > { // Read the files in this path
            const curPath = path + '/' + v
            if (fs.statSync(curPath).isDirectory()) {
                deleteFolderRecursive(curPath)  // If it is a folder, delete it recursively
            } else {
                fs.unlinkSync(curPath)  // Delete the file directly
            }
        })
        fs.rmdirSync(path)
    }
}

http.createServer(async (req, res) => {
    let projectDir = ' '
    if (req.method === 'POST' && req.url === '/') {
        console.log('Received request', req.url)
        projectDir = path.resolve(`. /${repository}`)    // Define the project directory
        deleteFolderRecursive(projectDir)       // Delete the cloned item recursively
        execSync(`git clone https://gitee.com/zhangfangbiao/${repository}.git`, {   // Start cloning
            stdio: 'inherit'
        })
        // Copy the Dockerfile to the project directory
        fs.copyFileSync(path.resolve('./Dockerfile'), path.resolve(projectDir, './Dockerfile'))
        // Copy.dockerignore to the project directory
        fs.copyFileSync(path.resolve('./.dockerignore'), path.resolve(projectDir, './.dockerignore'))
        // Create a mirror
        execSync(`docker build . -t ${repository}-image:latest`, {
            stdio: 'inherit'.cwd: projectDir,
        })
        // Destroy the container (find the container with the name starting with repository-container and stop it, then delete it)
        execSync(`docker ps -a -f "name=^${repository}-container" --format="{{.Names}}" | xargs -r docker stop | xargs -r docker rm`, {
            stdio: 'inherit',})// Create a new container (use repository-image:latest image to build the container and name it repository-container, locally mapping port 8888 to port 80 of the container)
        execSync(`docker run -d -p 8888:80 --name ${repository}-container  ${repository}-image:latest`, {
            stdio: 'inherit'
        })
        console.log('deploy success')
    }
    res.end(projectDir)
}).listen(3000.() = > {
    console.log('server is ready')})Copy the code

Start the server service

SCP. / index. Js [email protected]: / rootCopy the code

Then the server runs

# use pM2 to run the index.js script file in the background and name it webhook
pm2 start /root/index.js --name webhook
pm2 logs webhook
# check log
Copy the code

Trigger webhook

Pm2 log is now ready to print. Clone the code to build the image. When deploy success is printed, Open your browser to 47.103.74.196:8888 and you can see that the project is running. The whole process

  • Commit code and push
  • Gitee triggers a POST request to port 3000 of the server
  • Server receives request
  • Define the project directory, delete the original project, and clone the latest code
  • Copy Dockerfile and.dockerignore to the project directory
  • Create a mirror image
    • Using node Images
    • Set the working directory to /app
    • Copy the code to the working directory
    • Install dependencies
    • packaging
    • Use nginx images
    • Copy the build from /app/build to /usr/share/nginx/ HTML in the container
    • The container exposed port 80
    • Keep nginx foreground running in the container
  • Update the container to map port 80 of the container to local port 8888

Project address: gitee.com/zhangfangbi…