Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing container-friendly applications

Writing container-friendly applications

Presented at LinuxCon/ContainerCon North America 2015. (http://sched.co/44LN)

This talk is about how the OS primitives and sticking to the practices that has been out there since Unix philosophy came out helps us applying the best practices for configuration, logging, secrets, command line interfaces, output streams in writing application that are going to live in containers.

The talk goes through examples from the real-world popular open source projects and how they are containerized as official Docker images.

Ahmet Alp Balkan

August 18, 2015
Tweet

More Decks by Ahmet Alp Balkan

Other Decks in Technology

Transcript

  1. files > ./program config.cfg command-line args > ./program --debug --db

    127.0.0.1:3036 environment > DEBUG=1 DB=127.0.01:3036 ./program
  2. my.cnf (MySQL) [client] port = 3306 socket = /var/run/mysqld/mysqld.sock [mysqld]

    user = mysql pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock port = 3306 basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp lc-messages-dir = /usr/share/mysql explicit_defaults_for_timestamp # Instead of skip-networking the default is now to listen only on # localhost which is more compatible and is not less secure. #bind-address= 127.0.0.1
  3. cassandra.yaml cluster_name: 'Test Cluster' num_tokens: 256 hinted_handoff_enabled: true hinted_handoff_throttle_in_kb: 1024

    max_hints_delivery_threads: 2 batchlog_replay_throttle_in_kb: 1024 authenticator: AllowAllAuthenticator authorizer: AllowAllAuthorizer role_manager: CassandraRoleManager roles_validity_in_ms: 2000 permissions_validity_in_ms: 2000 data_file_directories: - /var/lib/cassandra/data commitlog_directory: /var/lib/cassandra/commitlog seed_provider: - class_name: org.apache.cassandra.locator.SimpleSeedProvider parameters: - seeds: "127.0.0.1" concurrent_reads: 32
  4. nginx.conf user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid;

    events { worker_connections 1024; } http { include /etc/nginx/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"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on;
  5. nginx.conf user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid;

    events { worker_connections 1024; } http { include /etc/nginx/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"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; W HAT LANGUAGE IS THIS?
  6. tip 0: do not write your own parser command line

    arguments the language already has one
  7. tip 1: have strict validation command line arguments $ server

    --host localhost --portt 5000 [INFO] Server started listening. typo!?
  8. tip 1: have strict validation command line arguments $ cassandra

    --help getopt: unrecognized option '--help' $ $ $ INFO 16:56:01 Loading settings from file:/etc/cassandra/cassandra.yaml INFO 16:56:01 Node configuration:[authenticator=AllowAllAuthenticator; authorizer=AllowAllAuthorizer; auto_snapshot=true; batch_size_fail_threshold_in_kb=50; batch_size_warn_threshold_in_kb=5; batchlog_replay_throttle_in_kb=1024; cas_contention_timeout_in_ms=1000; client_encryption_options=<REDACTED>; cluster_name=Test Cluster; column_index_size_in_kb=64; commit_failure_policy=stop; commitlog_directory=/var/lib/cassandra/commitlog;
  9. tip 2: print your configuration command line arguments $ server

    --host localhost --port 5000 [DEBUG] bind=localhost [DEBUG] port=5000 [DEBUG] workers=10 [DEBUG] logfile=/dev/stdout [INFO] Server started listening.
  10. tip 3: have good defaults command line arguments memcached: 32

    options, 0 required redis: 50 options, 0 required docker: 41 options, 0 required
  11. long command line arguments environment variables $ program \ --user

    root \ --password sEzMaB4UmnUMwLgHvikxmmUtco Ed2EkcYQUkdXHsC(CRt8ZHXKhMFxDyZtzNWeRF;92 72ymGGOh$hitUsFseBmbR
  12. long command line arguments environment variables $ program \ --user

    root \ --password sEzMaB4UmnUMwLgHvikxmmUtco Ed2EkcYQUkdXHsC(CRt8ZHXKhMFxDyZtzNWeRF;92 72ymGGOh$hitUsFseBmbR
  13. override arguments environment variables $ program --help Usage: server [OPTIONS]

    Options: --user Name of the victim for the container gods --password Authentication key [$PASSWORD]
  14. override arguments environment variables $ program --help Usage: server [OPTIONS]

    Options: --user Name of the victim for the container gods --password Authentication key [$PASSWORD]
  15. Doing IT WRONG # comment out a few problematic configuration

    values # don't reverse lookup hostnames, they are usually another container RUN sed -Ei 's/^(bind-address|log)/#&/' /etc/mysql/my.cnf \ && echo 'skip-host-cache\nskip-name-resolve' | \ awk '{ print } $1 == "[mysqld]" && c == 0 { \ c = 1; system("cat") }' /etc/mysql/my.cnf > /tmp/my.cnf \ && mv /tmp/my.cnf /etc/mysql/my.cnf MySQL Dockerfile uses sed/awk to disable a couple of configuration keys https://github.com/docker-library/cassandra
  16. Doing IT WRONG sed -ri 's/(- seeds:) "127.0.0.1"/\1 "'"$CASSANDRA_SEEDS"'"/' "$CASSANDRA_CONFIG/cassandra.yaml"

    for yaml in broadcast_address broadcast_rpc_address \ cluster_name endpoint_snitch listen_address num_tokens; do var="CASSANDRA_${yaml^^}" val=“${!var}”; if [ "$val" ]; then sed -ri 's/^(# )?('"$yaml"':).*/\2 '"$val"'/' \ "$CASSANDRA_CONFIG/cassandra.yaml" fi done fi exec "$@" Apache Cassandra entrypoint script override configs in YAML file from env https://github.com/docker-library/cassandra
  17. command line args environment variables let the user override with

    configuration files options 95% of users will not ever change:
  18. just log to stdout/stderr let your init system do it

    sysvinit runit upstart systemd supervisor monit
  19. just log to stdout/stderr let your init system do it

    sysvinit runit upstart systemd supervisor monit docker ✨ ✨ ✨ ✨
  20. Apache HTTP Server FROM debian:jessie RUN apt-get install [dependencies] RUN

    [get source code] RUN [compile it] && make install DOING IT WRONG
  21. Apache HTTP Server FROM debian:jessie RUN apt-get install [dependencies] RUN

    [get source code] RUN [compile it] && make install RUN sed -ri ' \ s!^(\s*CustomLog)\s+\S+!\1 /proc/self/fd/1!g; \ s!^(\s*ErrorLog)\s+\S+!\1 /proc/self/fd/2!g; \ ' /usr/local/apache2/conf/httpd.conf DOING IT WRONG
  22. Apache HTTP Server FROM debian:jessie RUN apt-get install [dependencies] RUN

    [get source code] RUN [compile it] && make install RUN sed -ri ' \ s!^(\s*CustomLog)\s+\S+!\1 /proc/self/fd/1!g; \ s!^(\s*ErrorLog)\s+\S+!\1 /proc/self/fd/2!g; \ ' /usr/local/apache2/conf/httpd.conf DOING IT WRONG
  23. nginx HTTP Server FROM debian:jessie […] ENV NGINX_VERSION 1.9.3-1~jessie RUN

    apt-get install nginx=${NGINX_VERSION} DOING IT WRONG
  24. nginx HTTP Server FROM debian:jessie […] ENV NGINX_VERSION 1.9.3-1~jessie RUN

    apt-get install nginx=${NGINX_VERSION} # forward logs to docker log collector RUN ln -sf /dev/stdout /var/log/nginx/access.log RUN ln -sf /dev/stderr /var/log/nginx/error.log * and 80 other things DOING IT WRONG
  25. nginx HTTP Server $ docker run --read-only nginx [emerg] open()

    "/var/run/nginx.pid" failed (30: Read-only file system) DOING IT WRONG
  26. DOING IT WRONG $ nginx ⏎ $ $ ps afx

    PID TTY STAT TIME COMMAND 1 ? Ss 0:00 bash 9 ? Ss 0:00 nginx: master process nginx 10 ? S 0:00 \_ nginx: worker process 13 ? R+ 0:00 ps afx what?
  27. DOING IT WRONG $ nginx ⏎ $ $ ps afx

    PID TTY STAT TIME COMMAND 1 ? Ss 0:00 bash 9 ? Ss 0:00 nginx: master process nginx 10 ? S 0:00 \_ nginx: worker process 13 ? R+ 0:00 ps afx what?
  28. FROM debian:jessie RUN apt-get install nginx=${NGINX_VERSION} … EXPOSE 80/tcp EXPOSE

    443/tcp ENTRYPOINT ["nginx", "-g", "daemon off;"] https://github.com/nginxinc/docker-nginx/ nginx Dockerfile DOING IT WRONG
  29. ASP.NET 5 Docker Image DOING IT WRONG Problem: “my container

    suddenly exits after running for 5 minutes”
  30. DOING IT WRONG var ignored = Task.Run(() => { Console.WriteLine("Started");

    Console.ReadLine(); appShutdownService.RequestShutdown(); }); Microsoft.AspNet.Hosting/Program.cs: ASP.NET 5 Docker Image https://github.com/aspnet/Hosting/
  31. DOING IT WRONG var ignored = Task.Run(() => { Console.WriteLine("Started");

    Console.ReadLine(); appShutdownService.RequestShutdown(); }); Microsoft.AspNet.Hosting/Program.cs: ASP.NET 5 Docker Image https://github.com/aspnet/Hosting/
  32. version: 0.1 log: fields: service: registry storage: cache: blobdescriptor: inmemory

    filesystem: rootdirectory: /var/lib/registry http: addr: :5000 tls: certificate: /path/to/x509/public key: /path/to/x509/private config.yml Docker Registry
  33. version: 0.1 log: fields: service: registry storage: cache: blobdescriptor: inmemory

    filesystem: rootdirectory: /var/lib/registry http: addr: :5000 tls: certificate: /path/to/x509/public key: /path/to/x509/private config.yml Docker Registry -e REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=… O verride
  34. Docker Swarm $ swarm join --help docker run -it swarm

    join --help Usage: swarm join [OPTIONS] <discovery> join a docker cluster Arguments: <discovery> discovery service to use [$SWARM_DISCOVERY] * token://<token> * consul://<ip>/<path> * etcd://<ip1>,<ip2>/<path> * file://path/to/file * zk://<ip1>,<ip2>/<path> * <ip1>,<ip2> Options: --advertise, address of this Docker Engine [$SWARM_ADVERTISE] --heartbeat “20s" period between each heartbeat --ttl "60s" sets the expiration of an ephemeral node
  35. Docker Swarm $ swarm join --help docker run -it swarm

    join --help Usage: swarm join [OPTIONS] <discovery> join a docker cluster Arguments: <discovery> discovery service to use [$SWARM_DISCOVERY] * token://<token> * consul://<ip>/<path> * etcd://<ip1>,<ip2>/<path> * file://path/to/file * zk://<ip1>,<ip2>/<path> * <ip1>,<ip2> Options: --advertise, address of this Docker Engine [$SWARM_ADVERTISE] --heartbeat “20s" period between each heartbeat --ttl "60s" sets the expiration of an ephemeral node