preface

A year ago, I renewed the server, the original server is the combination of Nginx + Jenkins + Docker, because of the strong interest in the updated tools, so I thought of reinstalling the new toy, began to use Traefik + DroneCi + Docker to replace the original combination of the road.

After the initial completion of service construction, I want to write this article to leave a memory of the pit I have encountered, and also hope to help other people who have just entered the pit.


Traefik

Traefik is an open source reverse proxy and load balancing tool. Many people compare Traefik to Nginx. In my case, traefik doesn’t support static sites well, so I still use it with Nginx. But that doesn’t detract from the fact that it’s an excellent reverse proxy tool.

Configuration and Startup

The Traefik configuration includes static configuration and dynamic configuration. The static configuration takes effect only after Traefik is restarted. The dynamic configuration is regarded as the proxy configuration and does not need to be restarted after modification. Cli and configuration files are supported for both dynamic and static configurations. However, configuration files and Cli parameters cannot be superimposed.

  • Static configuration

    For example, starting the Traefik service in docker-compose, we can take a look at the cli and configuration file:

    services:
      traefik:
        restart: always
        image: traefik:latest
        ports:
          - "80:80"
          - "443:443"
        # command:
        # - "--providers.docker=true"
        # - "--providers.docker.exposedbydefault=false"
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
          - ./acme.json:/acme.json
          - ./traefik/:/etc/traefik If there is a configuration file, command is invalid
      who:
        image: containous/whoami
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.whoa.rule=Host(`who.nefelibata.art)"
    Copy the code

    We configured the Traefik container and mapped the./traefik directory containing traefik.toml and dynamic.toml files to /etc/traefik of the container. Traefik reads traefik. Toml in /etc/traefik on startup, and if you want to use cli, you pass in configuration parameters through command.

  • Dynamic configuration

    • Configuration file format

      First we need the following configuration in Traefik. Toml:

      [providers]
        ## ...
        [providers.file]
          filename = "/etc/traefik/dynamic_conf.toml"
          watch = true
      Copy the code

      Configure routers and services in Dynamic_conf. toml

      [http.routers] [http.routers.https-egg] rule = "Host(`egg.nefelibata.art`)" service = "egg-service" [http.phones.http-egg] rule = "Host(' egg.nefelibata.art ')" service = "egg-service" [http.services] [[http.services.egg-service.loadBalancer.servers]] url = "http://egg_server:9000"Copy the code

      For example, HTTP. Phones.https -egg is used to enable TLS, but HTTP

      Note that we defined two routers for egg.nefelibata.art. This is because HTTP access is no longer supported if TLS is set to true. If you want to support both HTTP and HTTPS access, you need to define a route with a different name

    • Docker labels form

      Providers in traefik. Toml, if we don’t want to configure them all in a dynamic configuration file, we can write the following configuration:

      [providers] [providers. Docker] # network = "traefik" exposedByDefault = false defaultRule = "Host(' {{normalize ' .Name }}.nefelibata.localhost`)" watch = true ## ... Other configurationCopy the code

      For example, if you change the dynamic configuration to labels, it will look like this:

      egg_server:
          build: ./egg_server
          expose:
            - "9000"
          networks:
            - default
          labels:
            - "traefik.enable=true"
            - "traefik.http.routes.egg_server.rule=Host(`egg.nefelibata.art`)"
      Copy the code

      There is no need to configure services, just expose the port to the container.

      Note that if the exposedByDefault option is turned off in configuration, the container service will be ignored by Traefik if traefik.enable=true is not defined in labels of other containers

Open the Dashboard

Traefik comes with a Dashboard. If you want to start Traefik and configure a domain name for it, you can configure it dynamically as described above, using a profile as an example:

## traefik. Toml ## [API] ### Other configuration... providers, ping .etc ## dynamic.toml [http.routers.api] rule = "Host(`traefik.nefelibata.art`)" service = "api@internal" middlewares = ["dashboard-auth"] [http.middlewares] [http.middlewares.dashboard-auth.basicAuth] users = [ "evont: $xxxxxxxxxxx" ]Copy the code

When you enable the API option in Traefik. Toml (or on the CLI — API =true), traefik has a special service called api@internal. Once configured, traefik usually performs authentication to prevent access. So I add a middlewares, use Traefik’s basicAuth middleware, use htpasswd to generate a user key, note that your name is Evont, your password is 123456, The result is evont:$APR1 $bL6G3wl2$HllalTsbNwJ/zhoBMhx541. When you log in to Dashboard, the password is still 123456 instead of the key string.

After the configuration is successful, restart the service and open the domain name of the service. The login page is displayed

Static Website support

Traefik doesn’t support static sites very well (at least I haven’t found a way to use it), so I can only use Nginx as a server for static sites, but ports 443 and 80 can’t be used for both reverse proxies, so Traefik can only forward requests to Nginx. We start a Nginx service, specify networks so that Nginx and Traefik are in the same network, and then assign the defined domain names to Nginx as labels

services:
  nginx:
    restart: always
    image: nginx
    networks:
      - default
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./html:/usr/share/nginx/html/
    labels:
      - traefik.enable=true
      - traefik.http.routers.w3.rule=Host(`www.nefelibata.art`) || Host(`mock.nefelibata.art`)
      - traefik.http.routers.w3.tls=true
      - traefik.http.routers.w3.tls.certresolver=le
      - traefik.http.routers.w3-http.rule=Host(`www.nefelibata.art`) || Host(`mock.nefelibata.art`)

Copy the code

In the Nginx nginx.conf file, port 80 is still used and the directory of the static website is allocated according to server_name

http { server { listen 80; server_name www.nefelibata.art; root /usr/share/nginx/html/www1; location / { index index.html; } } server { listen 80; server_name mock.nefelibata.art; root /usr/share/nginx/html/www2; location / { index index.html; }}}Copy the code

HTTPS support (also HTTP support)

Certresolver = “le”; TLS = “HTTPS”;

If you want to automatically generate a certificate using Let’s Encrypt, Traefik provides a convenient solution. You can simply use the following configuration in static configuration:

## traefik. Toml ## [certificatesResolvers. Le. Acme] email = "[email protected]" # your email storage = "acme. Json" [certificatesResolvers.le.acme.httpChallenge] entryPoint = "http"Copy the code

In docker-compose, configure volumes to map acme.json to the docker-compose container

If you have your own certificate, you can also skip the above steps and configure it in dynamic.toml

[[tls.certificates]]
  certFile = "/path/to/domain.cert"
  keyFile = "/path/to/domain.key"
Copy the code

See Traefik’s TLS configuration for this section

Version of the pit

At the beginning of the configuration, most tutorials on the Internet were still based on V1, so the configuration was not successful. Later, IT was found that the configuration items were different between V1 and V2 versions, and even traefik Chinese documents were still based on V1 configuration, such as when defining routing rules

## v1 rule: [frontends] # # regulation front end into the rules/frontends. Frontend1 backend = "backend1" # specified back-end services [frontends. Frontend1. Routes] # # define routing [frontends. Frontend1. Routes. Route0] rule = "Host: test. The localhost" attention # #, Here writing also changed [backends] # # define back-end services [backends. Backend1] [backends. Backend1. The servers. Server0] url = "http://xx.xx.xx.xx:80" # # v2 [HTTP.router0] Rule = "Host(' test.localhost ')" ## Writing changed service = "my - service" [HTTP. Services] [[HTTP. Services. My - service. The loadBalancer. The servers]] url = "http://xx.xx.xx.xx:80"Copy the code

In addition, the above rules are defined in the [file] field in V1, and in V2, they are defined in the [providers. File] field in [providers] and become independent dynamic configuration files

In this regard, I recommend reading the official migration documentation and using the official documentation as a baseline (although it is also loosely written and not very friendly to those with poor English).


Drone CI

I used to use Jenkins, a CI tool of industry standard, but I always felt that it was a little cumbersome because of its rich functions. Drone has optimized the container environment of Docker and K8s, and is lightweight and flexible enough. If you don’t know how to choose between the two, you can read this article

OAuth

First of all, Drone only supports Git. Take Github as an example. To pull the code, you need to create an OAuth application in Github Developer Settings (you can use other Git repositories) and fill in the domain name of your Drone service. Note that the callback URL needs to be login

The installation

It is still based on the idea that all the services of the server are in Docker. A new Drone service is created in docker-comemage.yml. Many tutorials will configure drone-runner agent (agents), but it is not necessary. You can actually use drone-Server all by yourself

version: "3"

services:
  drone-server:
    image: drone/drone:latest
    labels:
      - traefik.http.routers.drone.rule=Host(`ci.nefelibata.art`)
      - traefik.enable=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /etc/docker/:/etc/docker
      - ./drone:/var/lib/drone/ Note that this directory is used to place SQLite files, if using mysql or another database, as appropriate
    restart: always
    networks:
      - default
    environment:
      - DRONE_OPEN=TRUE    
      - DRONE_ADMIN=xxx
      - DRONE_USER_CREATE=username:xxx,admin:true
      - DRONE_DATABASE_DATASOURCE=/var/lib/drone/drone.sqlite # point to the directory
      - DRONE_DATABASE_DRIVER=sqlite3 # database engine
      - DRONE_RPC_SECRET=${DRONE_RPC_SECRET}
      - DRONE_RPC_PROTO=${DRONE_RPC_PROTO}
      - DRONE_AGENTS_DISABLED=true 
      - DRONE_GITHUB_CLIENT_ID=${DRONE_GITHUB_CLIENT_ID}
      - DRONE_GITHUB_CLIENT_SECRET=${DRONE_GITHUB_CLIENT_SECRET}
      - DRONE_SERVER_HOST=${DRONE_SERVER_HOST}
      - DRONE_SERVER_PROTO=${DRONE_SERVER_PROTO}
networks:
  default:
    external:
      name: traefik
Copy the code

${XXX} ${XXX} ${XXX} ${XXX} ${XXX}

DRONE_GITHUB_CLIENT_ID=xxxxx  Enter the client ID generated by OAuth
DRONE_GITHUB_CLIENT_SECRET=xxxxx # fill in the OAuth generated client secret
DRONE_RPC_SECRET=xxxxx  This can be generated by openssl Rand-HEX 16
DRONE_SERVER_HOST=ci.nefelibata.art
DRONE_SERVER_PROTO=http
DRONE_RPC_PROTO=http
Copy the code

Drone registration is public by default, that is, all people who have access to your CI address can register and use your CI system. If you want to limit users, you can configure -drone_user_filter = Evont, XXX in the environment. Add users who are allowed to join (but not those who have previously registered, strange logic).

Start the service, access the service domain name, and jump to Github for login. If the login users are restricted in the previous step and the current Github account name is not allowed, the login fails when you return to the service.

If successful, you should see a list of your Github repository projects

Project settings
Trusted
Protected
Trusted
Volumes

The project build

Taking my project as an example, I hope that after submitting the code, Drone will help me build the mirror and then push it to the mirror warehouse.

Create a new project, after completing the basic code, create a following.drone.yml under the root directory of the project, use the Docker plug-in of drone, and specify the address and branch of the mirror warehouse.

Since you are using a private repository, you need to log in. At this time, we can fill in some variables in the Secrets column of CI interface for the construction process, so that we will not be exposed to.drone.yml. Add custom DOCKER_USERNAME and DOCKER_PASSWORD fields in the Secrets section as shown in the previous figure, then pass username and password through from_secret in.drone.yml, You don’t need to write it in a configuration file so that it can’t be seen by anyone else who has access to the code.

In addition, because the speed of pulling Docker images is very slow, you can specify the Docker acceleration source by setting the mirror.

---
kind: pipeline
type: docker
name: default


steps:
  - name: egg-docker
    image: plugins/docker
    settings: 
      mirror: https://xxxx(own user ID).mirror.aliyuncs.com
      username: 
        from_secret: DOCKER_USERNAME
      password:
        from_secret: DOCKER_PASSWORD 
      repo: registry.cn-hangzhou.aliyuncs.com/nefelibata/egg
      registry: registry.cn-hangzhou.aliyuncs.com
      auto_tag: true

trigger:
  branch:
    - master
  event:
    - push
Copy the code

Once confirmed, commit code to the project to trigger a build. If the version of Drone is larger than 1.4.0 and Agent is not enabled, you are likely to be stuck in Pending state like me. This is because Drone is in multi-machine mode by default. If it is a single server, you do not need to set a proxy server. Many configurations on the web teach DRONE_AGENTS_ENABLED=false, but DRONE_AGENTS_DISABLED=true should be used to enable single-machine mode.

Once set up and the build is triggered, you can see that the project has a build process, which pulls the project code into a temporary directory that will be destroyed when the build is complete.

Build complete, push to mirror warehouse, scatter flowers!


Docker

  • networks

    The containers of different Docker Compose are different. Each Docker Compose has its own networks. The services mentioned above are separated in different docker-comemess. yml files. We need to make them in the same network. In this case, we can first establish a shared network by executing Docker network Create Treaefik. Then configure networks in each docker-comemage. yml to point to the newly created networks, and finally specify its networks in the container

      services:
         nginx:
          #...
          image: nginx
          networks:
            - default
          #...
      networks:
        default:
          external:
            name: treaefik
    Copy the code
  • other

    There are many docker related articles, but I have not encountered any problems in this aspect in the project for the time being, which needs to be recorded.