Docker deploys a simple Web app

Create a simple Web project

package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte("hello wuyue")) }) server := &http.Server{ Addr: ":8812", } fmt.Println("server startup") if err := server.ListenAndServe(); err ! = nil { fmt.Println("server listen error") } }Copy the code

And then create our Dockerfile

FROM golang:alpine # Set the necessary environment variable ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS= Linux \ GOARCH=amd64 # move to the working directory workdir and CD This command is similar to WORKDIR /build # COPY the code in the current directory to the container COPY.. # compile our code into binary executable app RUN go build -o app. # move to /dist where the generated binary is stored RUN cp /build/app. # Declare the service port 8812 EXPOSE 8812 # ["/dist/app"]Copy the code

Take a look at the directory structure:

Then enter the command:

docker build . -t simple_go_web_app

So once we’ve made the image we can run the container

docker run -p 8814:8812 simple_go_web_app

Port A: indicates port B

Note that B is the port 8812 we specified when we wrote the Dockerfile and we can map this port to our native port 8814

Take a look at the effect:

What’s the advantage of that?

You can send your code to someone else, even if they don’t have the go environment installed, but see the Dockerfile, you can also enter the command to run the project is very convenient

Improved deployment – Build in phases

This is already convenient, but there are still some imperfections, because go is compiled as an executable file, the final run does not need to go compilation environment, a Linux can be

So let’s change that

FROM Golang :alpine AS Builder # Set the necessary environment variables ENV GO111MODULE=on CGO_ENABLED=0 GOOS= Linux GOARCH=amd64 This command is somewhat similar to workdir /build # COPY the code in the current directory into a container COPY.. # compile our code into a binary executable app RUN go build -o app. # COPY -- FROM =builder /build/app / # command to run CMD ["/dist/app"] ENTRYPOINT ["/app"]Copy the code

Here’s how far apart the two mirror images are

Add some static files to the project

Let’s simply use the default generated vUE code and put it directly into our project

Register the route as well:

r.LoadHTMLFiles("./html/dist/index.html")
r.Static("/css","./html/dist/css")
r.Static("/js","./html/dist/js")
r.Static("/img","./html/dist/img")

r.GET("/vue", func(context *gin.Context) {
   context.HTML(http.StatusOK, "index.html", nil)
})
Copy the code

Note that this kind of project is not the separation of the front and back end. Now there are few enterprise-level projects that should use this scheme. This is just to demonstrate the use of docker functions, and the deployment mode of the separation of the front and back end will be introduced later

Docker deployment with other static files

Write the dockerfile in our project:

FROM Golang :alpine AS Builder # Set the necessary environment variables ENV GO111MODULE=on CGO_ENABLED=0 GOOS= Linux GOARCH=amd64 This command is similar to workdir /build # COPY the go.mod and go.sum files in the project and download the dependency information before the simple project has no third-party dependencies so you can ignore this step COPY Go. Mod. COPY go. Sum. RUN go env - w GOPROXY = https://goproxy.cn, direct RUN go mod # download COPY code to COPY in the container. The. BBS RUN go build -o BBS. # Create a small image Linux will eventually RUN with this minimal image. COPY --from=builder /build/ BBS / # ENTRYPOINT ["/ BBS ","config.yaml"] # EXPOSE ["/dist/app"]Copy the code

then

docker build . -t bbs_app

Then run and take a look:

Mysql side made a mistake, obviously docker containers To not even my native mysql is connected, it’s ok, here we can continue to create mysql’s container

You can download a mysql image first

Then start our container:

Docker run -itd –name mysql-test -p 3309:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0.18

We can log in to the database as root 123456

In the meantime, modify our YAML files

Here is mainly to change our port number and password

At this time, you will find that at least the local machine can connect to the mysql database in docker, but we still need to import our previous BBS database into this database

Docker run-itd –name redis-test-p 16379:6379 redis docker run-itd –name redis-test-p 16379:6379 redis

At this point our native GO program can access docker inside the two databases.

It’s very convenient.

Modify dockerfile

The above is only to do the native GO access the docker database, then we have to do, go in docker can access the docker database

Continue to modify our YAML files

mysql:
  host: mysql-docker
  port: 3306
  user: "root"
  password: "123456"
  dbname: "bbs"
  max_open_connection: 200
  max_idle_connection: 10
redis:
  host: redis-docker
  passowrd: ""
  port: 6379
  db: 0
  pool_size: 0
Copy the code

Notice that our host is now a string, and we can define this string any way we want, but the only thing that matters is the port number

This port number represents the port number of the container, not the port number mapped from the container to the local machine.

Once this is done, we can rebuild the image:

Then start our container again:

docker run –link=mysql-test:mysql-docker –link=redis-test:redis-docker -p 8019:8016 bbs_app

Link A: B A: represents the name of our local container B: represents the host name configured in YAML

If you see this, it’s running successfully

Access static files: That’s fine, too.

Some attention to detail

Some of you may have noticed that the port number in the dockerfile is wrong. Expose actually you understand is a function of an annotation, it just tells the reader here to expose the port number to serve

Which port your container ends up on is determined by the command you use to start the container

Change the domestic data source for Docker

  "builder": {
    "gc": {
      "enabled": true,
      "defaultKeepStorage": "20GB"
    }
  },
  "registry-mirrors":[
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com" 

   ],



  "debug": true,
  "experimental": false
}
Copy the code

Docker Compose deployment

For example, compose should be composed instead of using the “–link” method to make the whole project run

We’ll start with an sh script that you can download on Github. The main function is to enable a process to wait until the previous process has started

wait-for.sh

#! /usr/bin/env bash # Use this script to test if a given TCP host/port are available WAITFORIT_cmdname=${0##*/} echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } usage() { cat << USAGE >&2 Usage: $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] -h HOST | --host=HOST Host or IP under test -p PORT | --port=PORT TCP port under test Alternatively, you specify the host and port as host:port -s | --strict Only execute subcommand if the test succeeds -q | --quiet Don't  output any status messages -t TIMEOUT | --timeout=TIMEOUT Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes USAGE exit 1 } wait_for() { if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" else echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" fi WAITFORIT_start_ts=$(date +%s) while : do if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then nc -z $WAITFORIT_HOST $WAITFORIT_PORT WAITFORIT_result=$? else (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 WAITFORIT_result=$? fi if [[ $WAITFORIT_result -eq 0 ]]; then WAITFORIT_end_ts=$(date +%s) echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" break fi sleep 1 done return $WAITFORIT_result } wait_for_wrapper() { # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 if [[ $WAITFORIT_QUIET -eq 1 ]]; then timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT  --timeout=$WAITFORIT_TIMEOUT & else timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & fi WAITFORIT_PID=$! trap "kill -INT -$WAITFORIT_PID" INT wait $WAITFORIT_PID WAITFORIT_RESULT=$? if [[ $WAITFORIT_RESULT -ne 0 ]]; then echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" fi return $WAITFORIT_RESULT } # process arguments while [[ $# -gt 0 ]] do case "$1" in *:* ) WAITFORIT_hostport=(${1//:/ }) WAITFORIT_HOST=${WAITFORIT_hostport[0]} WAITFORIT_PORT=${WAITFORIT_hostport[1]} shift 1 ;; --child) WAITFORIT_CHILD=1 shift 1 ;; -q | --quiet) WAITFORIT_QUIET=1 shift 1 ;; -s | --strict) WAITFORIT_STRICT=1 shift 1 ;; -h) WAITFORIT_HOST="$2" if [[ $WAITFORIT_HOST == "" ]]; then break; fi shift 2 ;; --host=*) WAITFORIT_HOST="${1#*=}" shift 1 ;; -p) WAITFORIT_PORT="$2" if [[ $WAITFORIT_PORT == "" ]]; then break; fi shift 2 ;; --port=*) WAITFORIT_PORT="${1#*=}" shift 1 ;; -t) WAITFORIT_TIMEOUT="$2" if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi shift 2 ;; --timeout=*) WAITFORIT_TIMEOUT="${1#*=}" shift 1 ;; --) shift WAITFORIT_CLI=("$@") break ;; --help) usage ;; *) echoerr "Unknown argument: $1" usage ;; esac done if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then echoerr "Error: you need to provide a host and port to test." usage fi WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} # Check to see if timeout is from busybox? WAITFORIT_TIMEOUT_PATH=$(type -p timeout) WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) WAITFORIT_BUSYTIMEFLAG="" if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then WAITFORIT_ISBUSY=1 # Check if busybox timeout uses -t flag # (recent Alpine versions don't support -t anymore) if timeout &>/dev/stdout | grep -q -e '-t '; then WAITFORIT_BUSYTIMEFLAG="-t" fi else WAITFORIT_ISBUSY=0 fi if [[ $WAITFORIT_CHILD -gt 0 ]]; then wait_for WAITFORIT_RESULT=$? exit $WAITFORIT_RESULT else if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then wait_for_wrapper WAITFORIT_RESULT=$? else wait_for WAITFORIT_RESULT=$? fi fi if [[ $WAITFORIT_CLI != "" ]]; then if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" exit $WAITFORIT_RESULT fi exec "${WAITFORIT_CLI[@]}" else exit $WAITFORIT_RESULT fiCopy the code

Then we need to prepare a SQL, in the newly started mysql docker, we want the mysql to be able to execute this SQL to ensure that when we start up the database will help us to create the corresponding database and table named init.sql

-- create the databases
CREATE DATABASE IF NOT EXISTS bbs;
Copy the code

I’m going to skip the copy and paste statements for the sake of simplicity. You get the idea

Then update our dockerfile

FROM Golang :alpine AS Builder # Set the necessary environment variables ENV GO111MODULE=on CGO_ENABLED=0 GOOS= Linux GOARCH=amd64 This command is similar to workdir /build # COPY the go.mod and go.sum files in the project and download the dependency information before the simple project has no third-party dependencies so you can ignore this step COPY Go. Mod. COPY go. Sum. RUN go env - w GOPROXY = https://goproxy.cn, direct RUN go mod # download COPY code to COPY in the container. The. BBS RUN go build -o BBS. # Create a small image Linux will eventually RUN with this minimal image FROM Debian :stretch-slim # COPY static file COPY Wait-for.sh.copy config.yaml.copy./ HTML/HTML # COPY build BBS from builder --from=builder /build/ BBS / # Don't forget to update the source Otherwise the build card when you need to RUN for a long time the echo 'deb http://mirrors.163.com/debian/ stretch the main non - free contrib' > /etc/apt/sources.list RUN echo 'deb http://mirrors.163.com/debian/ stretch-updates main non-free contrib' >> /etc/apt/sources.list RUN echo 'deb http://mirrors.163.com/debian-security/ stretch/updates main non-free contrib' >> /etc/apt/sources.list RUN set -eux; \ apt-get update; \ apt-get install -y \ --no-install-recommends \ netcat; \ chmod 755 wait-for.shCopy the code

You can carefully compare the difference between us -link that way dockerfile

Finally, the crucial step is to create our docker-comedy.yaml file

Version: "3.7" services: mysql-test2: image: "mysql:8.0.18" ports: - "13306:3306" Command: "--default-authentication-plugin=mysql_native_password --init-file /data/application/init.sql" environment: MYSQL_ROOT_PASSWORD: "123456" MYSQL_USER: "root" MYSQL_DATABASE: "bbs" volumes: - ./init.sql:/data/application/init.sql redis-test2: image: "redis:latest" ports: - "26379:6379" bbs_app2: build: The key here is to make sure that our web processes command after mysql and Redis start: sh -c "./wait-for.sh mysql-test2:3306 redis-test2:6379 -- ./bbs config.yaml" depends_on: - mysql-test2 - redis-test2 ports: - "8066:8016"Copy the code

Then type docker-compose up. It’s a lot more convenient than link did before

You have to be careful here

When you change the native code, run the Docker-compose build to build the new image, and then run the Docker-compose up -d to replace the running container

Otherwise you’ll find that Docker will always use the image you built before, and your changes won’t take effect. !!!!!

Deployment Solution 1

In general, we can compile Linux files locally and then copy them to your cloud server via SSH SCP along with your static files

Then remember nohup to start it, this is easier

Also remember to redirect the log to a file. Otherwise you nohup the program and you won’t see the log output

You can also use Supervisor to turn a normal command line process into a background daemon and monitor its running status

If a process exits unexpectedly, it can be restarted automatically

This scheme is the mainstream scheme at present.

I won’t extend it here, but if you are interested, you can search by yourself.

With nginx deployment

This article will not go into too much detail, because it is a simple configuration of nginx, just search, here are some simple keywords for you to know what to do when deploying server applications in Nginx

Nginx -t checks whether the configuration file is written correctly

Upstream Backend is used for load balancing.

Before, our Go program included some static files related to the front end. The previous deployment required these static interfaces to go to the GIN framework, which was not necessary

Normal enterprise deployment should be separated at the Nginx level, where static files go directly through the nginx proxy and are returned directly, and API requests go through your server code

Corresponding keyword

The location ~. * \. (GIF | JPG | js |) location try_files avoid refresh the page 404Copy the code

If you need to do your own search.

In addition, if your front end and back end are not under the same domain name, you may want to support cross-domain

You can refer to the following open source library for processing is to add a middleware go cross domain