[PYTHON] How to make a container name a subdomain and make it accessible in Docker

This article is the 11th day of Recruit Lifestyle Advent Calendar 2015 --Qiita. I'm moremagic, who is in charge of development at Hot Pepper Beauty. We are working in the app infrastructure team to help developers.

Introduction

Do you guys use Docker? I recently started using it, but it's insanely convenient!

However, as the number of containers increases, it is difficult to remember the port number. .. Also, it is awesome that you can only know which service you are accessing by port number. Instead of switching the container to access by port number Wouldn't it be easier to understand if the container name entered the domain name as it is?

So, this time I will talk about accessing with Docker with the container name as a subdomain.

What does it mean to make a container name a subdomain?

For example ,, The name of the server running Docker is ʻexample.com Container namedev-tomcat` Assuming port 8080 is forwarding to port 49000

Normally if you want to access port 8080 of the above dev-tomcat container Because port 8080 is forwarding to port 49000 Access as follows.

http://example.com:49000/

If there is only one container started, it's not difficult or anything. It's hard to remember when the number of containers and services increase. If you make a mistake, you will connect to a different service.

So it's easy for humans to understand

http://dev-tomcat-8080.example.com

It is a story to make it accessible as described above. In this case, you can access it if you remember the container name and the port that is actually operating in the container!

Strategy

Create and launch a name resolution container that makes the container name subdomain accessible within the server. Since it is realized by http proxy, subdomain resolution is possible only for http protocol.

docker-proxy.png

Just start the name resolution container and the Docker container information will be stored in the container on a regular basis. You can access by container name without rewriting the configuration file every time you start the container.

Premise

There is a Docker environment, and the host on which Docker is running can resolve the name. You have enabled Docker's RemoteAPI

procedure

  1. Create an Image with Docker
  2. Launch the container from Image
  3. Enjoy name resolution

Create an Image with Docker

Prepare the following files and create a Docker Image Place other than Dockerfile in an appropriate folder according to the description of Dockerfile.

Docker file

The Dockerfile looks like this. Install redis, python3, nginx based on ubuntu: 14.04 I make a startup shell and hit it at startup

Dockerfile


FROM ubuntu:14.04

~ Omitted ~

# python3 install
RUN apt-get install -y python3 python3-pip && apt-get clean
RUN pip3 install redis
ADD redis/regist.py /usr/sbin/regist.py
RUN chmod +x  /usr/sbin/regist.py

# redis
RUN apt-get update && apt-get install -fy redis-server
ADD redis/redis.conf /etc/redis/redis.conf

# nginx install
RUN apt-get -y install nginx lua-nginx-redis && apt-get clean
ADD nginx/default /etc/nginx/sites-available/
ADD nginx/rewrite.lua /etc/nginx/sites-available/
ADD nginx/cert/ /etc/nginx/cert/

#Create launch shell
RUN printf '#!/bin/bash \n\
/usr/bin/redis-server & \n\
/usr/sbin/regist.py > /dev/null & \n\
/etc/init.d/nginx start \n\
/etc/init.d/nginx reload \n\
/usr/sbin/sshd -D \n\
tail -f /var/null \n\
' >> /etc/service.sh \
    && chmod +x /etc/service.sh

EXPOSE 22 6379 80 443
CMD /etc/service.sh

nginx The key to the operation, the nginx configuration file looks like this. The server is waiting at 80,443 and is set to rewrite → proxy.

default


server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /usr/share/nginx/html;
    index index.html index.htm;

    server_name localhost;
    location / {
        set $upstream "";
        rewrite_by_lua_file /etc/nginx/sites-available/rewrite.lua;

        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://$upstream;

        client_max_body_size 200M;
    }
}

server {
    listen 443;
    server_name localhost;

    ssl on;
    ssl_certificate /etc/nginx/cert/ssl.crt;
    ssl_certificate_key /etc/nginx/cert/ssl.key;
    ssl_session_timeout 5m;

    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        set $upstream "";
        rewrite_by_lua_file /etc/nginx/sites-available/rewrite.lua;

        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass https://$upstream;

        client_max_body_size 200M;
    }
}

rewrite.lua Get the container name and port number from the host name. Contact redis and return the actual container

rewrite.lua


local routes = _G.routes

if routes == nil then
    routes = {}
    ngx.log(ngx.ALERT, "[[[Route cache is empty.]]")
end

local container_name = string.sub(ngx.var.http_host, 1, string.find(ngx.var.http_host, "%.")-1)
local route = routes[container_name]
if route == nil then
    local Redis  = require "nginx.redis"
    local client = Redis:new()

    client:set_timeout(1000)
    local ok, err = client:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "************ Redis connection failure: " .. err)
        return
     end

    route = client:get(container_name)
end

ngx.log(ngx.ALERT, route)

-- fallback to redis for lookups
if route ~= nil then
    ngx.var.upstream = route
    routes[container_name] = route
else
    ngx.log(ngx.ALERT, "=ng=[[[route null]]]")
    ngx.exit(ngx.HTTP_NOT_FOUND)
end

python It is a behind-the-scenes player who stores container information in Redis. It keeps updating container information every 3 seconds via Docker's Remote API. I've been running it in a loop all the time, but there may be a little more way. .. ..

regist.py


#!/usr/bin/python3

import os
import sys
import time
import json
import redis
import urllib.request

DOCKER_HOST = os.getenv('DOCKER_HOST')
REDIS_ADDR = '127.0.0.1'
REDIS_PORT = 6379


def redisDump():
  conn = redis.Redis(host=REDIS_ADDR, port=REDIS_PORT)
  for key in conn.keys():
    print(key)
    print(conn.get(key))
  return conn.keys()

def addData(datas):
  conn = redis.Redis(host=REDIS_ADDR, port=REDIS_PORT)
  for key in set(list(datas.keys()) + list(conn.keys())):
    if isinstance(key, bytes):
      key = key.decode('utf-8')
    if key in datas:
      conn.set(key, datas[key])
    else:
      conn.delete(key)

def getContainers():
  response = urllib.request.urlopen('http://' + DOCKER_HOST + '/containers/json?all=1')
  jsonData = json.loads(response.read().decode('utf-8'))

  datas = {}
  for con in jsonData:
    name = con['Names'][-1][1:]
    con_ip = getIpAddress(con['Id'])

    for port in con['Ports']:
      key = name + '-' + str(port['PrivatePort'])
      value=con_ip + ':' + str(port['PrivatePort'])
      datas[key] = value

  return datas


def getIpAddress(con_id):
  response = urllib.request.urlopen('http://' + DOCKER_HOST + '/containers/' + con_id + '/json')
  jsonData = json.loads(response.read().decode('utf-8'))
  #print(json.dumps(jsonData))
  ret = jsonData['NetworkSettings']['IPAddress']
  return ret

while True:
  addData(getContainers())
  print( redisDump() )
  sys.stdout.flush()
  time.sleep(3)

Build image

Go to the folder where the Dockerfile is stored and type the following command!

# docker build -t docker-discovery .

Launch the container from Image

Start the Image you created earlier. Since the container information is acquired using the DockerRemote API of Docker running on the host from inside the container, I will spell it at startup.

# docker run -d -p 80:80 -p 443: 443 -e DOCKER_HOST = <Docker Host IP address>: <RemoteAPI port> --name docker-discovery docker-discovery

  1. Forward ports 80 and 443.
  2. The environment variable DOCKER_HOST is specified

This is all you need to do name resolution!

Enjoy name resolution

Suppose the host running docker is example.com You can connect to the corresponding container at the following URL.

http: // {container name}-{port number in the public container} .example.com

Let's start the TeamCity container as a trial. docker run -dP --name teamcity moremagic/teamcity

This container exposes port 8111 to the outside world, so you can access it at the URL below. http://teamcity-8111.example.com

image

Enjoy Docker!

Recommended Posts

How to make a container name a subdomain and make it accessible in Docker
How to use Decorator in Django and how to make it
How to delete a Docker container
I want to create a pipfile and reflect it in docker
How to get a specific column name and index name in pandas DataFrame
Flutter in Docker-How to build and use a Flutter development environment inside a Docker container
[Python] How to name table data and output it in csv (to_csv method)
How to read a serial number file in a loop, process it, and graph it
How to run a Django application on a Docker container (development and production environment)
You can do it in 3 minutes! How to make a moving QR code (GIF)!
How to use Docker to containerize your application and how to use Docker Compose to run your application in a development environment
Foreigners talk: How to name classes and methods in English
How to implement Python EXE for Windows in Docker container
How to make a Japanese-English translation
How to interactively draw a machine learning pipeline with scikit-learn and save it in HTML
How to make a slack bot
How to make a crawler --Advanced
How to make a recursive function
How to make a deadman's switch
[Blender] How to make a Blender plugin
How to make a crawler --Basic
How to log in to Docker + NGINX
How to install OpenCV on Cloud9 and run it in Python
How to compare lists and retrieve common elements in a list
How to split and save a DataFrame
Qiita (1) How to write a code name
[Python] How to make a class iterable
How to get a stacktrace in python
How to make a Backtrader custom indicator
How to make a Pelican site map
How to use is and == in Python
How to make a model for object detection using YOLO in 3 hours
How to get a namespaced view name from a URL (path_info) in Django
How to make a request to bitFlyer Lightning's Private API in Go language
How to stop a program in python until a specific date and time
[Python] How to save the installed package and install it in a new environment at once Mac environment
How to input a character string in Python and output it as it is or in the opposite direction.
How to make a dialogue system dedicated to beginners
How to generate a QR code and barcode in Python and read it normally or in real time with OpenCV
How to clear tuples in a list (Python)
How to embed a variable in a python string
[Introduction to Pandas] Read a csv file without a column name and give it a column name
How to create a JSON file in Python
How to make a dictionary with a hierarchical structure.
How to reflect ImageField in Django + Docker (pillow)
How to count the number of elements in Django and output to a template
Make a chatbot and practice to be popular.
How to make a QGIS plugin (package generation)
How to save the feature point information of an image in a file and use it for matching
How to notify a Discord channel in Python
I read "How to make a hacking lab"
Load a photo and make a handwritten sketch. With zoom function. Tried to make it.
[Python] How to draw a histogram in Matplotlib
How to create a Rest Api in Django
How to write async and await in Vue.js
How to write a named tuple document in 2020
A command to specify a file with a specific name in a directory with find and mv, cp, or gzip it (linux)
How to count numbers in a specific range
[Pandas] How to check duplicates and delete duplicates in a table (equivalent to deleting duplicates in Excel)
How to install Cascade detector and how to use it
How to read a file in a different directory