Running Node.js Apps with Docker/Docker Compose

Checklist of dockerizing an application

Choose a base image

Consider to use Alpine images.

Install the necessary packages

  • You need to write apt-get update and apt-get install on the same line (same if you are using apk on Alpine).
  • Double check if you are installing ONLY what you really need (assuming you will run the container on production).

Add your custom files

  • Understand the different between COPY and ADD
  • Follow File System conventions on where to place your files
  • Check the attributes of the files you are adding. If you need execution permission, there is no need to add a new layer on your image (RUN chmod +x). Just fix the original attributes on your code repository.

Define which user will (or can) run your container

  • Without any other option provided, processes in containers will execute as root (unless a different uid was supplied in the Dockerfile).
  • You only need to run your container with a specific (fixed ID) user if your application need access to the user or group tables (/etc/passwd or /etc/group).
  • Avoid running your container as root as much as possible.
  • Note some applications require you to run them with specific ids (e.g. Elastic Search with uid:gid = 1000:1000).

Define the exposed ports

Define the entrypoint

Create a docker-entrypoint.sh script where you can hook things like configuration using environment variables.

Examples

Define a Configuration method

Every application requires some kind of parametrization. There are basically two paths you can follow:

  • Use an application specific configuration file: them you will need to document the format, fields, location and so on (not good if you have a complex environment, with applications spanning different technologies).
  • Use (operating system) Environment variables: Simple and efficient.

Externalize your data

The golden rule is: do not save any persistent data inside the container.

The container file system is supposed and intended to be temporary, ephemeral. So any user generated content, data files, process output should be saved either on a mounted volume or on a bind mounts (that is, on a folder on the Base OS linked inside the container).

Make sure you handle the logs as well

If you are creating a new app and want it to stick to docker conventions, no logs files should be written at all. The application should use stdout and stderr as an event stream.

Docker will automatically capture everything you are sending to stdout and make it available through “docker logs” command.

Rotate logs and other append only files

If your application is writing log files or appending any files that can grow indefinitely, you need to worry about file rotation.

This is critical for you to prevent the server running out of space, apply data retention policies (which is critical when it comes to GDPR and other data regulations).

Example of log rotation using logrotate

Lean image with Docker multi-stage build

Docker 17.05 extends Dockerfile syntax to support new multi-stage build, by extending two commands: FROM and COPY.

The multi-stage build allows using multiple FROM commands in the same Dockerfile. The last FROM command produces the final Docker image, all other images are intermediate images (no final Docker image is produced, but all layers are cached).

The FROM syntax also supports AS keyword. Use AS keyword to give the current image a logical name and reference to it later by this name.

To copy files from intermediate images use COPY --from=<image_AS_name|image_number>, where number starts from 0 (but better to use logical name through AS keyword).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#
# ---- Base Node ----
FROM alpine:3.5 AS base
# install node
RUN apk add --no-cache nodejs-current tini
# set working directory
WORKDIR /root/chat
# Set tini as entrypoint
ENTRYPOINT ["/sbin/tini", "--"]
# copy project file
COPY package.json .

#
# ---- Dependencies ----
FROM base AS dependencies
# install node packages
RUN npm set progress=false && npm config set depth 0
RUN npm install --only=production
# copy production node_modules aside
RUN cp -R node_modules prod_node_modules
# install ALL node_modules, including 'devDependencies'
RUN npm install

#
# ---- Test ----
# run linters, setup and tests
FROM dependencies AS test
COPY . .
RUN npm run lint && npm run setup && npm run test

#
# ---- Release ----
FROM base AS release
# copy production node_modules
COPY --from=dependencies /root/chat/prod_node_modules ./node_modules
# copy app sources
COPY . .
# expose port and define CMD
EXPOSE 5000
CMD npm run start

Docker Compose for NodeJS Development

An important concept to understand is that Docker Compose spans “buildtime” and “runtime”. Up until now, we have been building images using docker build ., which is “buildtime.” This is when our containers are actually built. We can think of “runtime” as what happens once our containers are built and being used.

Compose triggers “buildtime” — instructing our images and containers to build — but it also populates data used at “runtime,” such as env vars and volumes. This is important to be clear on. For instance, when we add things like volumes and command, they will override the same things that may have been set up via the Dockerfile at “buildtime.”

How to debug a Node.js application in a Docker container

References


Dockerfile

Docker 可以通过 Dockerfile 的内容来自动构建镜像。Dockerfile 是一个包含创建镜像所有命令的文本文件,通过 docker build 命令可以根据 Dockerfile 的内容构建镜像。

Dockerfile

FROM

  • FROM 必须是 Dockerfile 中非注释行的第一个指令,即一个 Dockerfile 从 FROM 语句开始。
  • FROM 可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像。

RUN

有两种使用方式

  • RUN (the command is run in a shell - /bin/sh -c - shell form)
  • RUN [“executable”, “param1”, “param2”] (exec form)

每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像,后续的 RUN 都在之前 RUN 提交后的镜像为基础,镜像是分层的,可以通过一个镜像的任何一个历史提交点来创建,类似源码的版本控制。

exec 方式会被解析为一个 JSON 数组,所以必须使用双引号而不是单引号。exec 方式不会调用一个命令 shell,所以也就不会继承相应的变量,如:
RUN [ "echo", "$HOME" ]

这种方式是不会达到输出 HOME 变量的,正确的方式应该是这样的
RUN [ "sh", "-c", "echo", "$HOME" ]

RUN 产生的缓存在下一次构建的时候是不会失效的,会被重用,可以使用 –no-cache 选项,即 docker build –no-cache,如此便不会缓存。

CMD

CMD 有三种使用方式:

  • CMD [“executable”,”param1”,”param2”] (exec form, this is the preferred form, 优先选择)
  • CMD [“param1”,”param2”] (as default parameters to ENTRYPOINT)
  • CMD command param1 param2 (shell form)

CMD 指定在 Dockerfile 中只能使用一次,如果有多个,则只有最后一个会生效。

CMD 的目的是为了在启动容器时提供一个默认的命令执行选项。如果用户启动容器时指定了运行的命令,则会覆盖掉 CMD 指定的命令。

CMD 会在启动容器的时候执行,build 时不执行,而 RUN 只是在构建镜像的时候执行,后续镜像构建完成之后,启动容器就与 RUN 无关了。

EXPOSE

EXPOSE <port> [<port>...]

告诉 Docker 服务端容器对外映射的本地端口,需要在 docker run 的时候使用 -p 或者 -P 选项生效。

ENV

ENV <key> <value> # 只能设置一个变量
ENV <key>=<value> ... # 允许一次设置多个变量

指定一个环节变量,会被后续 RUN 指令使用,并在容器运行时保留。

1
2
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy

等同于

1
2
3
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

ADD

ADD <src>... <dest>

ADD 复制本地主机文件、目录或者远程文件 URLS 从 并且添加到容器指定路径中

支持通过 GO 的正则模糊匹配,具体规则可参见 Go filepath.Match

1
2
ADD hom* /mydir/        # adds all files starting with "hom"
ADD hom?.txt /mydir/ # ? is replaced with any single character

路径必须是绝对路径,如果 不存在,会自动创建对应目录

路径必须是 Dockerfile 所在路径的相对路径

如果是一个目录,只会复制目录下的内容,而目录本身则不会被复制

COPY

COPY <src>... <dest>

COPY 复制新文件或者目录从 添加到容器指定路径中 。用法同 ADD,唯一的不同是不能指定远程文件 URLS。

ENTRYPOINT

1
2
ENTRYPOINT [“executable”, “param1”, “param2”] (the preferred exec form,优先选择)
ENTRYPOINT command param1 param2 (shell form)

配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖,而 CMD 是可以被覆盖的。如果需要覆盖,则可以使用 docker run –entrypoint 选项。

每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个生效。

Exec form ENTRYPOINT 例子

  1. 通过 ENTRYPOINT 使用 exec form 方式设置稳定的默认命令和选项,而使用 CMD 添加默认之外经常被改动的选项。

    1
    2
    3
    FROM ubuntu
    ENTRYPOINT ["top", "-b"]
    CMD ["-c"]
  2. 通过 Dockerfile 使用 ENTRYPOINT 展示前台运行 Apache 服务

    1
    2
    3
    4
    5
    FROM debian:stable
    RUN apt-get update && apt-get install -y --force-yes apache2
    EXPOSE 80 443
    VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
    ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

Shell form ENTRYPOINT 例子

  1. 这种方式会在 /bin/sh -c 中执行,会忽略任何 CMD 或者 docker run 命令行选项,为了确保 docker stop 能够停止长时间运行 ENTRYPOINT 的容器,确保执行的时候使用 exec 选项。

    1
    2
    FROM ubuntu
    ENTRYPOINT exec top -b
  2. 如果在 ENTRYPOINT 忘记使用 exec 选项,则可以使用 CMD 补上:

    1
    2
    3
    FROM ubuntu
    ENTRYPOINT top -b
    CMD --ignored-param1 # --ignored-param2 ... --ignored-param3 ... 依此类推

VOLUME

VOLUME [“/data”]
创建一个可以从本地主机或其他容器挂载的挂载点

USER

USER daemon
指定运行容器时的用户名或 UID,后续的 RUN、CMD、ENTRYPOINT 也会使用指定用户。

WORKDIR

WORKDIR /path/to/workdir
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
最终路径是 /a/b/c。

WORKDIR 指令可以在 ENV 设置变量之后调用环境变量:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
最终路径则为 /path/$DIRNAME。

dockerfile 最佳实践

使用 .dockerignore 文件

为了在 docker build 过程中更快上传和更加高效,应该使用一个 .dockerignore 文件用来排除构建镜像时不需要的文件或目录。例如,除非 .git 在构建过程中需要用到,否则你应该将它添加到 .dockerignore 文件中,这样可以节省很多时间。

避免安装不必要的软件包

为了降低复杂性、依赖性、文件大小以及构建时间,应该避免安装额外的或不必要的包。例如,不需要在一个数据库镜像中安装一个文本编辑器。

每个容器都跑一个进程

在大多数情况下,一个容器应该只单独跑一个程序。解耦应用到多个容器使其更容易横向扩展和重用。如果一个服务依赖另外一个服务,可以参考 Linking Containers Together。

最小化层

我们知道每执行一个指令,都会有一次镜像的提交,镜像是分层的结构,对于 Dockerfile,应该找到可读性和最小化层之间的平衡。

多行参数排序

如果可能,通过字母顺序来排序,这样可以避免安装包的重复并且更容易更新列表,另外可读性也会更强,添加一个空行使用 \ 换行:

1
2
3
4
5
6
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion

创建缓存

镜像构建过程中会按照 Dockerfile 的顺序依次执行,每执行一次指令 Docker 会寻找是否有存在的镜像缓存可复用,如果没有则创建新的镜像。如果不想使用缓存,则可以在 docker build 时添加 –no-cache=true 选项。

从基础镜像开始就已经在缓存中了,下一个指令会对比所有的子镜像寻找是否执行相同的指令,如果没有则缓存失效。在大多数情况下只对比 Dockerfile 指令和子镜像就足够了。ADD 和 COPY 指令除外,执行 ADD 和 COPY 时存放到镜像的文件也是需要检查的,完成一个文件的校验之后再利用这个校验在缓存中查找,如果检测的文件改变则缓存失效。RUN apt-get -y update 命令只检查命令是否匹配,如果匹配就不会再执行更新了。

为了有效地利用缓存,你需要保持你的 Dockerfile 一致,并且尽量在末尾修改。

Dockerfile 指令

FROM: 只要可能就使用官方镜像库作为基础镜像

RUN: 为保持可读性、方便理解、可维护性,把长或者复杂的 RUN 语句使用 \ 分隔符分成多行

不建议 RUN apt-get update 独立成行,否则如果后续包有更新,那么也不会再执行更新

避免使用 RUN apt-get upgrade 或者 dist-upgrade,很多必要的包在一个非 privileged 权限的容器里是无法升级的。如果知道某个包更新,使用 apt-get install -y xxx

标准写法
RUN apt-get update && apt-get install -y package-bar package-foo

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
btrfs-tools \
build-essential \
curl \
dpkg-sig \
git \
iptables \
libapparmor-dev \
libcap-dev \
libsqlite3-dev \
lxc=1.0* \
mercurial \
parallel \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.0*

CMD: 推荐使用 CMD [“executable”, “param1”, “param2”…] 这种格式,CMD [“param”, “param”] 则配合 ENTRYPOINT 使用

EXPOSE: Dockerfile 指定要公开的端口,使用 docker run 时指定映射到宿主机的端口即可

ENV: 为了使新的软件更容易运行,可以使用 ENV 更新 PATH 变量。如 ENV PATH /usr/local/nginx/bin:$PATH 确保 CMD [“nginx”] 即可运行

ENV 也可以这样定义变量:

1
2
3
4
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ADD or COPY: ADD 比 COPY 多一些特性「tar 文件自动解包和支持远程 URL」,不推荐添加远程 URL
如不推荐这种方式:

1
2
3
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

推荐使用 curl 或者 wget 替换,使用如下方式:

1
2
3
4
RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.gz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all

如果不需要添加 tar 文件,推荐使用 COPY。

References


Docker

Concepts

Docker Commands

Images

  • 一个只读模板,可以用来创建容器,一个镜像可以创建多个容器
  • Docker 提供了一个很简单的机制来创建和更新现有的镜像,甚至可以直接从其他人那里获取做好的镜像直接使用

Docker File System

  • docker 镜像代表了容器的文件系统里的内容,是容器的基础,镜像一般是通过 Dockerfile 生成的
  • docker 的镜像是分层的,所有的镜像(除了基础镜像)都是在之前镜像的基础上加上自己这层的内容生成的
  • 每一层镜像的元数据都是存在 json 文件中的,除了静态的文件系统之外,还会包含动态的数据

Commands

Search on Docker store
docker search mysql

Pull an image from Docker store
docker pull mysql:latest

Push an image to Docker store
docker push

List all downloaded images
docker images

Delete an image
docker rmi mysql
docker rmi mysql tomcat

Delete all iamges
docker rmi $(docker images -q)

Build an image from Dockerfile
docker build

Tag an image
docker tag

Container

  • 容器是从镜像创建的运行实例,也就是镜像启动后的一个实例称为容器,是独立运行的一个或一组应用。
  • docker 利用容器来运行应用,他可以被启动、开始、停止、删除,每个容器都是相互隔离的、保证安全的平台。

Commands

Create and Run a container from an image
docker run --name container-name -d image-name

List all running containers
docker ps

Stop a running container
docker stop container-name/container-id

Force stop a running container
docker kill container-name/container-id

docker stop: Stop a running container (send SIGTERM, and then SIGKILL after grace period) […] The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL. [emphasis mine]
docker kill: Kill a running container (send SIGKILL, or specified signal) […] The main process inside the container will be sent SIGKILL, or any signal specified with option –signal. [emphasis mine]

Create a container but do not run it
docker create -t -i fedora bash

Start a container
docker start container-name/container-id

Restart a container
docker restart container-name/container-id

Delete a container
docker rm container-id

Delete all containers
docker rm $(docker ps -a -q )

Check logs
docker logs container-id/container-name

Attach to a running container
docker attach container-id

Run a command on a running container
docker exec -it container-id/container-name bash

List running processes on a container
docker top container-id

Inspect a container
docker insepct container-id

Copy file from host to container
docker cp 文件 container-id:目标文件/文件夹
docker cp /tmp/suzhuji.txt 7f237caad43b:/tmp

Copy file from container to host
docker cp container-id:目标文件/文件夹 宿主机目标文件/文件夹
docker cp 7f237caad43b:/tmp/yum.log /tmp

Resoisitory

  • 仓库是集中存放镜像文件的场所,类似 git 代码仓库等。
  • 仓库(Respository)和仓库注册服务器(Registry)是有区别的。仓库注册服务器一般存放多个仓库,每个仓库又有多个镜像,每个镜像又有不同的标签(tag)。
  • 仓库分为公开仓库(public)和私有仓库(private)两种形式。
  • 当创建好自己的镜像后,可以通过 push 命令把它上传到公开或私有仓库。

Port mappping

1
2
# Find IP address of container with ID <container_id> 通过容器 id 获取 ip
$ sudo docker inspect <container_id> | grep IPAddress | cut -d ’"’ -f 4

自动映射端口

-P 使用时需要指定 –expose 选项,指定需要对外提供服务的端口

$ sudo docker run -t -P --expose 22 --name server ubuntu:14.04
使用 docker run -P 自动绑定所有对外提供服务的容器端口,映射的端口将会从没有使用的端口池中 (49000..49900) 自动选择,你可以通过 docker ps 、docker inspect <container_id> 或者 docker port <container_id> 确定具体的绑定信息。

绑定端口到指定接口

$ sudo docker run -p [([<host_interface>:[host_port]])|(<host_port>):]<container_port>[/udp] <image> <cmd>
默认不指定绑定 ip 则监听所有网络接口。

绑定 TCP 端口

1
2
3
4
5
6
7
8
# Bind TCP port 8080 of the container to TCP port 80 on 127.0.0.1 of the host machine.
$ sudo docker run -p 127.0.0.1:80:8080 <image> <cmd>
# Bind TCP port 8080 of the container to a dynamically allocated TCP port on 127.0.0.1 of the host machine.
$ sudo docker run -p 127.0.0.1::8080 <image> <cmd>
# Bind TCP port 8080 of the container to TCP port 80 on all available interfaces of the host machine.
$ sudo docker run -p 80:8080 <image> <cmd>
# Bind TCP port 8080 of the container to a dynamically allocated TCP port on all available interfaces
$ sudo docker run -p 8080 <image> <cmd>

绑定 UDP 端口

1
2
# Bind UDP port 5353 of the container to UDP port 53 on 127.0.0.1 of the host machine.
$ sudo docker run -p 127.0.0.1:53:5353/udp <image> <cmd>

Networking

Docker 四种网络模式

host 模式

如果启动容器的时候使用 host 模式,那么这个容器将不会获得一个独立的 Network Namespace,而是和宿主机共用一个 Network Namespace。容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。

例如,我们在 10.10.101.105/24 的机器上用 host 模式启动一个含有 web 应用的 Docker 容器,监听 tcp 80 端口。当我们在容器中执行任何类似 ifconfig 命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用 10.10.101.105:80 即可,不用任何 NAT 转换,就如直接跑在宿主机中一样。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。

container 模式

这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。

none模式

这个模式和前两个不同。在这种模式下,Docker 容器拥有自己的 Network Namespace,但是,并不为 Docker容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡、IP、路由等信息。需要我们自己为 Docker 容器添加网卡、配置 IP 等。

bridge模式

bridge 模式是 Docker 默认的网络设置,此模式会为每一个容器分配 Network Namespace、设置 IP 等,并将一个主机上的 Docker 容器连接到一个虚拟网桥上。当 Docker server 启动时,会在主机上创建一个名为 docker0 的虚拟网桥,此主机上启动的 Docker 容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。接下来就要为容器分配 IP 了,Docker 会从 RFC1918 所定义的私有 IP 网段中,选择一个和宿主机不同的IP地址和子网分配给 docker0,连接到 docker0 的容器就从这个子网中选择一个未占用的 IP 使用。如一般 Docker 会使用 172.17.0.0/16 这个网段,并将 172.17.42.1/16 分配给 docker0 网桥(在主机上使用 ifconfig 命令是可以看到 docker0 的,可以认为它是网桥的管理接口,在宿主机上作为一块虚拟网卡使用)

Volume

Data Volume

数据卷的使用其实和 Linux 挂载文件目录是很相似的。简单来说,数据卷就是一个可以供容器使用的特殊目录。

sudo docker run -ti --name volume1 -v /myDir ubuntu:16.04 bash
sudo docker run -ti --name volume2 -v /home/zsc/Music/:/myShare ubuntu:16.04 bash
sudo docker run -ti --name volume2 -v /home/zsc/Music/:/myShare:ro ubuntu:16.04 bash

数据卷是用来持久化数据的,所以数据卷的生命周期独立于容器。所以在容器结束后数据卷并不会被删除,如果你希望删除数据卷,可以在使用 docker rm 命令删除容器的时候加上 -v 参数。

值得注意的是,如果你删除挂载某个数据卷的所有容器的同时没有使用 -v 参数清理这些容器挂载的数据卷,你之后再想清理这些数据卷会很麻烦,所以在你确定某个数据卷没有必要存在的时候,在删除最后一个挂载这个数据卷的容器的时候,使用 -v 参数删除这个数据卷。

Data Volume Container

所谓数据卷容器,其实就是一个普通的容器,只不过这个容器专门作为数据卷供其它容器挂载。

首先,在运行 docker run 指令的时候使用 -v 参数创建一个数据卷容器(这和我们之前创建数据卷的指令是一样的):
sudo docker run -ti -d -v /dataVolume --name v0 ubuntu:16.04

然后,创建一个新的容器挂载刚才创建的数据卷容器中的数据卷:使用 –volumes-from 参数
sudo docker run -ti --volumes-from v0 --name v1 ubuntu:16.04 bash

Tricks

Clean disk space used by Docker

  • docker system df
  • docker system prune
  • docker system prune -a

References


Kubernetes Ingress and Traefik

Why

Kubernetes 为每个 Pod 分配了唯一的 IP(即:Pod IP),Pod 里的多个容器共享这个 IP。Pod 内的容器除了 IP,还共享相同的网络命名空间、端口、存储卷等,也就是说这些容器之间能通过 Localhost 来通信。Pod 包含的容器都会运行在同一个节点上,也可以同时启动多个相同的 Pod 用于 Failover 或者 Load balance。

Pod 的生命周期是短暂的,Kubernetes 会根据应用的配置对 Pod 进行创建、销毁并根据监控指标进行伸缩扩容。Kubernetes 在创建 Pod 时可以选择集群中的任何一台空闲的节点上进行,因此其网络地址是不固定的。由于 Pod 的这一特点,一般不建议直接通过 Pod 的地址去访问应用。

为了解决访问 Pod 不方便直接访问的问题,Kubernetes 采用了 Service 对 Pod 进行封装。Service 是对后端提供服务的一组 Pod 的抽象,Service 会绑定到一个固定的虚拟 IP上。该虚拟 IP 只在 Kubernetes Cluster 中可见,但其实该虚拟 IP 并不对应一个虚拟或者物理设备,而只是 IPtables 中的规则,然后再通过 IPtables 将服务请求路由到后端的 Pod 中。通过这种方式,可以确保服务消费者可以稳定地访问 Pod 提供的服务,而不用关心 Pod 的创建、删除、迁移等变化以及如何用一组 Pod 来进行负载均衡。

实现 Service 这一功能的关键是由 Kubernetes 中的 Kube-Proxy 来完成的。Kube-Proxy 运行在每个节点上,监听 API Server 中服务对象的变化,再通过管理 IPtables 来实现网络的转发。

Read More


Microservices Data Architecture

Problems

  • One database per service or One shared database?
  • One type of database system or multiple types(SQL, NoSQL, etc.)
    • Polyglot Persistence or Multi-model Database
  • Scalability

Polyglot Persistence vs Multi-model Database

Scale Cube

MongoDB

MongoDB 是一个分布式文档型数据库,它有以下一些特性使它非常适合于微服务架构:

  • 多模数据库 (Multi-model)
  • 原生 JSON 数据结构 - API
  • 动态模式、无模式 (Dynamic schema / Schemaless)
  • 数据变化流 (Change Stream)
  • 横向扩展能力 (Sharding)


GraphQL Authentication/Authorisation And Error Handling With Apollo

Authentication/Authorisation

Where could we do access controll?

  • on Express router not on apollo itself when running on HTTP
    • get user/token with an auth middleware before reaching /graphql endpoint
    • user/token will be put in the graphql context for later use(fine-grained authorisation etc.)
  • on model layer
    • recommended by Facebook
    • good if you have a separate model layer API, for example a set of Restful APIs
  • wrapper queries
  • custom directives

Error Handling

How do we handle errors?

  • errors handled in the standard errors array on the response body with a consistent machine-readable structure
  • use error types defined by apollo-server, with custom names