next-generation microservice containers
what this is *not* about
- Re-architecturing LCP/BGT
- not about making LCP "reactive"
- or Kafka, Redis, CouchDB2, etc
- Changing the deployment mechanism
- not about the new CI/CD pipeline
- or migrating to k8s
what this is about
- Codifying the lessons we learned deploying containers in production
- "Better practices"
- Re-eval Fabrika as our development and deployment protocol
- Paving the way for next-generation infrastructure
- Making our containers conducive to being deployed to the cloud
"12-factor" containers
Derived as guiding principles
One imaGE to rule them all
1
Same image for app and deployment
(no more deployment images)
in fact, same image for managerial tasks as well
- analysis
- unit test
- integration test
- build image
- publish image
- run
- deploy
no more rebuilding image for dev/rc/prod
- Subject to vuln scan, integrity check, etc
- "Promoted" to a release candidate
configuration via environment vars
2
do not mount /config from host
into the container
- not cloud-friendly
- hosts can come and go
- should embrace ephemerality
- make as few assumptions about the host as possible
- may get out of sync
- Rethink what is *actually* a config
How much of this file is *actually* configuration?
Use stdout as
log stream
3
corollary to the previous principle
Do not mount logs folder in the container
Write everything to stdout
Let docker log driver decide the destination of the logs
- syslog
- splunk
rethink base images
base image defines the capabilities
- Is the image for a Python microservice?
- Does the image need nscd?
base image provides extension points
- Impose contract so services can be built in a consistent way
- But, services can also extend its capability through its own Dockerfile
- ONBUILD directive
- Defers the execution of the command until the build time of the child image
FROM python:2.7.11
MAINTAINER infrastructure-team@points.com
ENV GOSU_VERSION 1.9
ENV APP_GROUP=webgroup APP_USER=webuser
RUN DEBIAN_FRONTEND=noninteractive
# Install nscd
...
# Install dumb-init
...
# Install gosu
...
# Add webapp user and group
...
WORKDIR /content
COPY ./timezone /etc/timezone
COPY ./entrypoint.sh /content/entrypoint.sh
ONBUILD COPY . /content
ONBUILD RUN chown -R $APP_USER:$APP_GROUP /content
ONBUILD RUN for r in $(ls requirements); do pip install -r requirements/$r; done
ONBUILD ARG service_name
ONBUILD ARG git_commit_hash
ONBUILD LABEL com.points.service_name=$service_name com.points.git_commit_hash=$git_commit_hash
ENTRYPOINT ["dumb-init", "/content/entrypoint.sh"]
CMD ["-l"]
FROM base-docker.points.com/python_service_onbuild
Dockerfile of Buy Service
Rethink
how services are run
we don't need apache mod_wsgi
- Causes dev and prod disparity
- flask dev server during dev and apache in the container
- Only has sync worker
- We run a reverse proxy anyway (haproxy)
- Tightly coupled to Apache httpd
we want to have an app server
- Can run and debug locally the same way using the same server software as container
- Support sync and async worker types for different work loads
- Gunicorn, uwsgi
we don't need supervisord
- Necessary when we used to run apache and rsyslogd
- Violates the container SRP
- Another layer of abstraction
- supervisord has its own configuration and set of signals to respond to
we want to have a
container init system
- Lightweight solution to the "pid 1" problem
- Normal processes do not reap zombies, only init does
- tini, dumb-init
an entrypoint
to tie these together
Entrypoint
library
Provides a consistent interface and reusable components for entrypoints of dockerized mciroservices at Points
entrypoint interface
tasks common to all microservices
- static_analysis
- test_unit
- test_integration
- build
- publish
- deploy
- run
- test_sanity
wait, is this another fabrika?
design tenets
Build Library,
Not framework
- Resist the urge to write classes, use inheritance or use template method pattern
- Favour composition over extension
- Limit the amount of options a dev is exposed to, while providing sane defaults
- Favour using CLI over API to reduce cognitive load
- It should be obvious what the tool is doing
- Every stage can be called independently as a task
- As opposed to hiding functionality behind Fabrika API
- Do not hide necessary details
- Fabrika generates Dockerfile as part of the build process
- Fabrika builds app image during integration test
Transparency
- Entrypoint tasks should be invokable both inside and outside of a docker container
- e.g., ``invoke test_unit`` should produce the same result as ``docker run IMAGE test_unit``
isomorphicity
- Builtin retry of idempotent operations
- e.g., docker pull, docker run, setup load balancer
resiliency
implementation
Still in Early Stage...
python
invoke
- Written by the same author as Fabric
- Task invocation library for (in dev) Fabric 2.0
- Simple interface, doesn't try to do too much
- e.g., no SSH remote execution
- Easy to write CLI with GNU calling convention
-
fab run_server:port=9090,check=1
-
inv run_server --port=9090 --check
-
why not fabric?
- Too many external dependencies
- e.g., paramiko, pycrypto, cryptography for supporting SSH remote execution
- Remote execution is better done with direct SSH commands
- More Straightforward
- Less dependencies to install (paramiko, pycrypto and friends)
- Can be reproduced by Copy/Paste the command and run it directly
why not bash/makefile?
- Adequate for small-scale scripts
- Becomes unmaintainable as the script grows
- Hard to share code between projects
- Harder to write unit tests
- Requires making a lot of mistakes, sweat and tears to become a bash expert
- Much so for Makefile
nothing is set in stone
- If there's compelling reason to use something else, we will consider it
- The advantage of invoke being a thin wrapper around pure python functions is that it's easily replaceable
https://github.com/points/entrypoint
https://github.com/Points/docker/tree/master/images/python_service_onbuild
microservice-container-ng
By Kevin Jing Qiu
microservice-container-ng
- 1,027