Docker realizes resource isolation through Namespace of Linux, resource control by Cgroups, and efficient file operation through copy-on-write mechanism. In practical development, Docker can be used to provide one-time environment, build microservice architecture, and deploy unified environment.

preface

Although Docker has become a popular container technology around the world, unifying the environment to avoid environment configuration is one of the main attractions of Docker, but there are still many problems when using it. For example, I have thought about these problems when building my own container:

  • How do I use the Jenkins container?
  • If using the Jenkins container, how do I deploy the SpringBoot project from the Jenkins container? Is the project deployed through the Jenkins container interacting with files in the SpringBoot container? Can it be done? Or put the SpringBoot project into a Jenkins container and install Git, Maven, etc., which is not convenient.
  • Use IDEA Docker plug-in can directly connect to the Docker server to create images and run containers, why also need Jenkins?

In the actual construction and deployment also found the corresponding answer:

  • If Jenkins containers are used, this can make deployment more difficult because Jenkins often needs to configure a number of variables, such as Maven and Git, and should find another way out. Since Jenkins is a script CI tool, and Docker also has its own scripts, I should consider the integration of Docker scripts into Docker.
  • In actual development, Jenkins may need not only the deployment of the project, but also the authentication of the developer. For example, developer A can only view the deployment of the specified project, and the administrator can view the deployment of all projects, but Docker is mainly used for image construction and container operation. There is no access to Github/GitLab code like Jenkins and no developer authentication, so Docker can only play a role in Jenkins to simplify the deployment process.
  • Although IDEA plug-in can directly package the successful project deployment server Dcoker locally and create the image running container, for security, it is also necessary to create Docker CA authentication, download it to the local and then connect the Docker on the server, which is very inconvenient

Environment to prepare

When the answers to the self-questions are explored, the main responsibilities of each component are identified:

  • Jenkins: Receive project updates and execute project packaging and Docker scripts
  • Docker: Install the required application image and run container
  • Git: Project information synchronization

Setting up the environment process:

  1. Install the JDK

  2. Install Maven

  3. Install git

  4. If you have permission problems, change the JENKINS_USER file in /etc/sysconfig/Jenkins to root or manually assign permissions

  5. Install Docker Engine – Community

  6. Install DockerCompose

    sudo curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    sudo chmod +x /usr/local/bin/docker-compose
    sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
    docker-compose --version
    Copy the code

    DockerCompose can save you the trouble of executing docker runs multiple times as containers grow

Specific steps

  • The configuration file

    1. SpringBoot project Dockerfile

    FROM java:8
    
    MAINTAINER Wilson
    
    # Unify container and server time
    ENV TZ=Asia/Shanghai
    RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
    
    # copy target/docker-spring-boot.jar to container app
    COPY ./target/docker-spring-boot.jar app/docker-spring-boot.jar
    EXPOSE 8080
    
    ENTRYPOINT ["java"."-jar"."app/docker-spring-boot.jar"]
    # docker build -t docker-spring-boot/latest .
    Copy the code

    2. Configuration docker – compose. Yml

    version: '3.7'
    services:
      app:
        restart: always
        build: ./
        hostname: docker-spring-boot
        container_name: docker-spring-boot
        image: docker-spring-boot/latest
    The port is not open to the public
    # ports:
    # - 8080:8080volumes: - ./volumes/app:/app nginx: depends_on: - app container_name: docker-nginx hostname: docker-nginx image: Nginx :1.17.6 Environment: TZ: Asia/Shanghai Restart: always expose: -80 ports: -80 links: -app volumes: nginx:1.17.6 Environment: TZ: Asia/Shanghai Restart: always expose: -80 ports: -80 links: -app volumes: - ./volumes/nginx/nginx.conf:/etc/nginx/nginx.conf - ./volumes/nginx/conf.d:/etc/nginx/conf.d - ./volumes/nginx/logs:/var/log/nginx
    Copy the code

    3. Nginx

    • ./volumes/nginx/nginx.conf

      user nginx;
      worker_processes 2; Set the value to the same as the number of CPU cores
      error_log /etc/nginx/error.log crit; # Log location and log level
      pid /etc/nginx/nginx.pid;
      
      events
      {
        use epoll;
        worker_connections 65535;
      }
      http{
          include mime.types;
          default_type application/octet-stream;
          log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for" "$http_cookie"';
      
          access_log  /var/log/nginx/access.log main;
          #charset utf8;
      
          server_names_hash_bucket_size 128;
          client_header_buffer_size 32k;
          large_client_header_buffers 4 32k;
          client_max_body_size 8m;
      
          sendfile on;
          tcp_nopush on;
          keepalive_timeout 60;
          tcp_nodelay on;
          fastcgi_connect_timeout 300;
          fastcgi_send_timeout 300;
          fastcgi_read_timeout 300;
          fastcgi_buffer_size 64k;
          fastcgi_buffers 4 64k;
          fastcgi_busy_buffers_size 128k;
          fastcgi_temp_file_write_size 128k;
          gzip on;
          gzip_min_length 1k;
          gzip_buffers 4 16k;
          gzip_http_version 1.0;
          gzip_comp_level 2;
          gzip_types text/plain application/x-javascript text/css application/xml;
          gzip_vary on;
      
          #limit_zone crawler $binary_remote_addr 10m;
          Server virtual host configuration
          include /etc/nginx/conf.d/*.conf;
      
      }
      Copy the code
    • /volumes/nginx/conf.d directory default.conf

      upstream application {
         server docker-spring-boot:8080;
      }
      
      server{
        listen 80;# monitor port
        server_name localhost;# the domain name
        access_log /var/log/nginx/nginx-spring-boot.log;
        location / {
            proxy_pass   http://application;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}Copy the code
  • Jenkins deployment execution process

    • Maven packages the Spring Boot project as project.jar
    • Depending on whether the following processes are performed with the first project deployment:
      • If the current mounted volume already contains the project JAR (that is, it is not run for the first time), run the following steps:
      1. Copy project.jar to overwrite the project.jar in the mounted volume
      2. Re-run the SpringBoot project container
      • If the current mounted volume does not contain the project JAR (that is, not the first run), run the following steps:
      1. Create a directory for mounting a volume
      2. Copy project.jar to the mounted volume
      3. Docker-compose read docker-compose. Yml configuration create image start container

    Jenkins script (Nginx container restart command can also be added if the Nginx configuration changes a lot) :

    cd /var/lib/jenkins/workspace/docker-spring-boot/spring-boot-nginx-docker-demo
    mvn clean package
    if [ -e "./volumes/app/docker-spring-boot.jar" ]
      then rm -f ./volumes/app/docker-spring-boot.jar \
            && cp ./target/docker-spring-boot.jar ./volumes/app/docker-spring-boot.jar \
    		&& docker restart docker-spring-boot \
            && echo "update restart success"
      elsemkdir volumes/app -p \ && cp ./target/docker-spring-boot.jar ./volumes/app/docker-spring-boot.jar \ && docker-compose -p  docker-spring-boot up-d \
            && echo "first start"
    fi
    Copy the code

    Docker-compose up command can be used for image installation, so it saves the trouble of preparing image-related instructions in advance when only docker command is used.

Results of the test

  • Check whether containers are started:docker ps
  • SpringBoot container run result If the container has port 8080 open, it can pass the http://url:8080/swagger-ui.html test. You can also check the SpringBoot log result of /volumes/app in Jenkins’ workspace. The path of SpringBoot log is set to app/logs. The app directory in the container has been mounted to the volumes/app directory of the current project.
  • Nginx container run result: Visit http://url/swagger-ui.html to check whether the Nginx container successfully connected to the SpringBoot container and reverse proxy. You can also check the /volumes/ Nginx /logs Nginx log verification results in the Jenkins workspace
  • Add or remove the Controller interface and push it to Git to see if the changed interface is accessible

The SpringBoot cluster is set up

To build SpringBoot from a container cluster, just make the following changes:

  • Docker-comemess. yml adds SpringBoot project redundancy, changes the redundant container name, distinguishes the log mount path, and changes the redundant container name

    version: '3.7'services: app: restart: always build: ./ hostname: docker-spring-boot container_name: docker-spring-boot image: docker-spring-boot/latest volumes: - ./volumes/app/docker-spring-boot.jar:/app/docker-spring-boot.jar - ./volumes/app/logs:/app/logs app-bak: restart: always build: ./ hostname: docker-spring-boot container_name: docker-spring-boot-bak image: docker-spring-boot/latest volumes: - ./volumes/app/docker-spring-boot.jar:/app/docker-spring-boot.jar - ./volumes/app/logs-bak:/app/logs nginx: Depends_on: -app container_name: docker-nginx hostname: docker-nginx image: nginx:1.17.6 Environment: TZ: - app container_name: docker-nginx hostname: docker-nginx image: nginx:1.17.6 Environment: TZ: Asia/Shanghai restart: always expose: - 80 ports: - 80:80 links: - app - app-bak volumes: - ./volumes/nginx/nginx.conf:/etc/nginx/nginx.conf - ./volumes/nginx/conf.d:/etc/nginx/conf.d - ./volumes/nginx/logs:/var/log/nginx
    Copy the code
  • Nginx changes upstream in default.conf to add redundant container configuration

    upstream application {
       server docker-spring-boot:8080 fail_timeout=2s max_fails=2 weight=1;
       server docker-spring-boot-bak:8080 fail_timeout=2s max_fails=2 weight=1;
    }
    
    server{
      listen 80;# monitor port
      server_name localhost;# the domain name
      access_log /var/log/nginx/nginx-spring-boot.log;
      location / {
          proxy_pass   http://application;
          proxy_connect_timeout 2s;
          proxy_set_header Host $host:$server_port;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header REMOTE-HOST $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}Copy the code
  • Jenkins added redundant container restart scripts

    BUILD_ID=DONTKILLME
    cd /var/lib/jenkins/workspace/docker-spring-boot/spring-boot-nginx-docker-demo
    mvn clean package
    if [ -e "./volumes/app/docker-spring-boot.jar" ]
      then rm -f ./volumes/app/docker-spring-boot.jar \
            && cp ./target/docker-spring-boot.jar ./volumes/app/docker-spring-boot.jar \
    		&& docker-compose -p docker-spring-boot up -d \
    		&& docker restart docker-spring-boot \
    		&& docker restart docker-spring-boot-bak \
    		&& docker restart docker-nginx \
            && echo "update restart success"
      elsemkdir volumes/app -p \ && cp ./target/docker-spring-boot.jar ./volumes/app/docker-spring-boot.jar \ && docker-compose -p  docker-spring-boot up-d \
            && echo "first start"
    fi
    Copy the code

Test cluster effect:

  • Volumes /app put logs for different containers, such as logs and logs-bak in this example
  • Stop any SpringBoot containerdocker stop docker-spring-boot, can still be accessed via Nginx via URL/API

You can see the following benefits of container-configured clustering:

  • High security. Each application belongs to only one container and can interact with hosts and other containers only through specific configurations
  • Unified configuration file, simple and crude way to solve the port, path, version and other configuration problems, such as the project even if the two 8080 port of the SpringBoot container without worrying about port conflicts, exposure problems, everything is solved in the container
  • Manual application installation is omitted to facilitate migration. Since version, configuration and environment have been configured in the Docker configuration file, there is no need to worry about various configuration and environment problems after machine replacement. Besides, the installation and configuration of applications such as Nginx, Redis and Mysql can be omitted through image pulling and container running

Attached project address

spring-boot-nginx-docker-demo