[docker compose] from building an image to running a Java project with one click

Keywords: MySQL Docker Spring Boot jar docker compose

Let's start with a question. How many steps does it take for a new machine to run a regular spring boot project?

1. Download and configure java environment, mysql environment and redis environment (step 6)

2. Initialize mysql database and import sql files (step 2)

3. Download the jar package and start (step 2)

As like as two peas Ling Wancheng's more than 10 steps, which are roughly divided into 00 fragments, and each one is complicated, and changing a machine is exactly the same way to waste time. So today, we introduce the use of docker-compose to complete [docker win (linux)) and one instruction completion.

What is docker?

Based on the problem, simply explain that the environment is a very painful thing in the win10 system. All kinds of software and all kinds of environments, online blogs, how to configure the environment, of course, is to find the big guy around you to drink a cup of milk tea and let him who is proficient in configuring and installing all kinds of software to help you. At the same time, there are many such ideas on the Internet. Let professional people do professional things to save some repetitive and troublesome things, such as knowledge payment, agent looking for resources, Taobao computer remote assistance service and so on.

The idea of docker is based on the above and integrated with the idea of container image (virtual machine). Since someone has configured and gone through the process, I directly encapsulate his process, configured environment and required dependencies (image), and then others can use it and run it directly (similar to script, you can execute it once (image) It can produce the same operation effect as the image publisher), which is stored in the container, and the container can be created multiple times based on the image. This is docker.

This article will build its own docker compose from scratch [containing many big pits]

github repository link [example project]

Docker compose one click operation [example project]

  • How to build your own image, and warehouse?
    • The springboot project is packaged to create an image based on jdk8
    • tag the image and upload it to the dockerhub warehouse
  • Docker compose construction and solution of various environmental problems.
    • Create docker compose file
    • Pull jar package image - pull mysql image - pull redis image
    • Container initialization sequence problem solving
    • jar package failed to access the database across containers

1. Create a jar package image

1.1 idea packages the springboot project into Jar package

java development foundation. Jar package is the encapsulated spring boot program, which is started with java jar command

The jar can be packaged by maven package

The jar package of this article is: face-0.0.1-SNAPSHOT.jar

1.2 creating images

Create the following files in the same directory as the jar package

Name it Dockerfile. After reading and writing, remember to delete the following txt

# Docker image for springboot file run
# VERSION 1.0.0
# Author: 404name
# The basic image uses java
FROM java:8
# author
MAINTAINER 404name<name404@163.com>
# Mapping file
VOLUME /tmp
# Change your jar package to app.jar
ADD face-0.0.1-SNAPSHOT.jar app.jar
# This refers to the port filled in the jar package, which can be mapped and exposed
EXPOSE 8080
# Startup script
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

1.3 building images

First, there must be a docker environment [link]

Run under current directory

docker build -t your_image_name .

for example

docker build -t 404name/sign-in-system:1.0.0 .

Here, 404name corresponds to the user name of my dockerhub, followed by the warehouse Name: version. The dot indicates the current directory

2. Submit image

2.1 create warehouse

Docker Hub Log in and register ~ create a warehouse, similar to github

2.2 labeling images

docker tag [REPOSITORY]:[TAG] [repo]/[REPOSITORY]:[TAG]

For example, your docker id is 404name, and you created a REPOSITORY called sign in system to submit version 1.0.0

Then: docker tag 404name / sign in system: 1.0.0 404name / sign in system: 1.0.0

2.3 submit after docker login

docker push 404name/sign-in-system:1.0.0

(then you can download it like github)

docker pull helloQMX/test:3

Seeing the above push and pull, have you thought of git? Docker uses similar warehouse management to manage images. Leaders around the world send their encapsulated environment. If others want to use it, docker pull xxx can pull a complex image to run on their own machine.

3. Writing docker compose script

There are many pits, and each step should be understood, illuminated or optimized

Based on the previous java:8 jar package image, it is publicly available for download. Here, write a script to add the mysql + redis environment in the warehouse

  1. Create docker-compose.yml to write content
  2. Create a peer folder docker compose
  1. The subordinate creates / mysql/initdb/xxx.sql. Put the initialization SQL file
  2. The subordinate creates / redis/redis.conf, and the write configuration is as follows

3.1 configuration content

File directory

├─docker-compose (Map container file)
│  ├─mysql
│  │  ├─conf
│  │  │      my.cnf
│  │  │
│  │  └─initdb
│  │          face_sign_in.sql
│  │
│  └─redis
│          redis.conf
│  docker-compose.yml(Main script)
│  wait-for-it.sh(Listen for other service startup)

wait-for-it.sh (online open source monitoring script)

#!/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
fi

docker-compose.yml (note below)

version: "3"
# Build service
services:
  # The first service pulls the jar package
  webapp:
    # Just packaged image
    image: 404name/sign-in-system:1.0.0
    ports:
      - "8080:8080"
    volumes:
      # Local files: mapping container files
      - "./wait-for-it.sh:/wait-for-it.sh"
      - "/data"
    links:
      # Create a channel link service so that there is no need to find ip across containers. The jar environment directly uses mysql:3306 link
      - mysql:mysql
      - redis:redis
    # The following two are executed after they are created, but they will not be executed until the following two are created. It is easy to cause the problem that the database has not been initialized
    depends_on: 
      - redis
      - mysql
    # Wait for mysql to execute the jar package. If it fails with a small probability, just run it again
    command: ./wait-for-it.sh mysql:3306 --timeout=60

  redis:
    image: redis:latest
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - ./docker-compose/redis/redis.conf:/etc/redis/redis.conf
    command: redis-server /etc/redis/redis.conf
    
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: face_sign_in
      MYSQL_USER: face_sign_in
      MYSQL_PASSWORD: 123456
      MYSQL_ROOT_PASSWORD: 123456
    expose:
      - 3306
    ports:
      - 3306:3306
    # In order to prevent Chinese garbled code, the following startup parameters are added
    volumes:
      # Start initialization sql file
      - ./docker-compose/mysql/initdb:/docker-entrypoint-initdb.d/
      # db persistence
      - mysql_db:/var/lib/mysql
      # To prevent exceptions in importing sql files, specify the format
      - ./docker-compose/mysql/conf/my.cnf:/etc/my.cnf
      
volumes:
  mysql_db:
    driver: local

my.cnf

[mysqld]
user=mysql
default-storage-engine=INNODB
character-set-server=utf8mb4
[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4

redis.conf

bind 0.0.0.0
protected-mode yes
port 6379
tcp-backlog 511
daemonize no
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
databases 16
always-show-logo yes
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
replica-serve-stale-data yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-diskless-load disabled
repl-disable-tcp-nodelay no
replica-priority 100
acllog-max-len 128
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
lazyfree-lazy-user-del no
oom-score-adj no
oom-score-adj-values 0 200 800
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
jemalloc-bg-thread yes

3.2 pit stepping point [attention!]

3.2.1 the mysql database cannot be found after the jar package is started

The database link address cannot be filled in either localhost:3306 or 127.0.0.1. Because containers have their own ip addresses, the external exposure is 127.0.0.1:3306, but local links cannot be used for cross container access

Method 1: find the msql container ip and modify the jar package database link

Method 2: fill in the host ip address for the database link (suitable for servers with public ip)

[the ip address of the above two methods should be determined in advance, so it is not suitable to change the machine, so the third method is troublesome]

Method 3: automatically query mysql

  1. Set link append first? useSSL=fasle
  2. For the configuration reference, set the link link to mysql above, and then fill in myqsl:3306 for the database link, so that mysql automatically maps the container ip without filling in it yourself.

3.2.2 incomplete database initialization

You need to specify the character set, utf8mb4 and refer to the above my.cnf folder and warehouse mapping for configuration

3.2.3 the jar package has been started before the database initialization. It is linked to the database, but the table cannot be found

Set the startup sequence, use the wait-for-it.sh script, specify the mysql:3306 response such as jar package, and restart (but if it fails with a small probability, you can start it again)

3.3 operation effect

So far, it has been completed.

The current folder can be completed with one click by running docker compose up (it can also be encapsulated into bat and click Run).

Alicloud hosted image

github is slow and dockerhub is slow. If you want to use gitee, you can try alicloud image hosting~

Alicloud login - welcome to alicloud, a secure and stable cloud computing service platform

1. Log in to alicloud Docker Registry

$ docker login --username=404**** registry.cn-hangzhou.aliyuncs.com

The user name used for login is the full name of the alicloud account, and the password is the password set when the service is opened.

You can modify the credential password on the access credentials page.

2. Pull the image from the Registry

$docker pull registry.cn-hangzhou.aliyuncs.com/404name/test: [image version number]

3. Push the image to Registry

$docker login -- username = 404 * * * * registry.cn-hangzhou.aliyuncs.com ` ` $docker tag [ImageID] registry.cn-hangzhou.aliyuncs.com/404name/test: [image version number] ` ` $docker push registry.cn-hangzhou.aliyuncs.com/404name/test: [image version number]

Please replace the [ImageId] and [image version number] parameters in the example according to the actual image information.

4. Select the appropriate mirror warehouse address

When pushing images from ECS, you can choose to use the intranet address of the image warehouse. The push speed will be improved and your public network traffic will not be lost.

If the machine you are using is located on the classic network, please log in with registry-internal.cn-hangzhou.aliyuncs.com as the domain name of the Registry.

If the machine you are using is located on the VPC network, please log in with registry-vpc.cn-hangzhou.aliyuncs.com as the domain name of the Registry.

5. Examples

Use the "docker tag" command to rename the image and push it to the Registry through the VPC address.

$ docker images``REPOSITORY                                                         TAG                 IMAGE ID            CREATED             VIRTUAL SIZE``registry.aliyuncs.com/acs/agent                                    0.7-dfb6816         37bb9c63c8b2        7 days ago          37.89 MB``$ docker tag 37bb9c63c8b2 registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816

Use the "docker push" command to push the image to the remote.

$ docker push registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816

Posted by ryochiji on Wed, 17 Nov 2021 20:19:44 -0800