Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Best practices

Raúl Sánchez edited this page Aug 18, 2017 · 3 revisions

Purpose

Some packages aren't build in the most efficient way to take profit of all rancher catalog features.

This document is intended to be a guide to build a community catalog following best practices. It start showing a basic initial package and will guide on how to improve it adding recommended practices.

Initial package

Lets start with a simple example of a web service with a db for persistence.

Definition

  • docker-compose.yml
version: '2'
services:
  web:
    image: <web_image>:latest
    ports:
      - ${web_port}:80
    environment:
      DB_HOST: db
      DB_PORT: ${db_port}
      DB_NAME: demo
      DB_USER: user
      DB_PASS: pass
    links:
      - db:db
  db:
    image: <db_image>
    ports:
      - ${db_port}:${db_port}
    environment:
      DB_PORT: ${db_port}
      DB_NAME: demo
      DB_USER: user
      DB_PASS: pass

Note: DB_HOST could be static value. db link is the variable part.

  • rancher-compose.yml
version: '2'
catalog:
  name: <name>
  version: <version>
  description: <description>
  uuid: <uuid>
  questions:    
    - variable: web_port
      description: "public port to access the web"
      label: "Web Port"
      required: true
      default: "80"
      type: "int"
    - variable: db_port
      description: "public port to access the db"
      label: "Db Port"
      required: true
      default: "3306"
      type: "int"
services:
  web:
    scale: 1
    retain_ip: true
  db:
    scale: 1
    retain_ip: true

Improving package

Lets see package improvements points and recommended practices.

Use concrete images versions

Packages has to be easy reproducible and latest isn't. Use a concrete version for every single service. Control package version with package version.

  • Set web image image: <web_image>:<web_version>

  • Set db image image: <db_image>:<db_version>

Use healthchecks

Tcp and http healtchecks are available. When you start a service with healthcheck, service has an additional state that is Initializing, that is the time between you start the container until service is started. Once healthcheck return ok, service state transition from Initializing to Started.

  • http health check for web service. Needs 2XX or 3XX response code.
    health_check:
      port: 80
      interval: 5000
      unhealthy_threshold: 3
      initializing_timeout: 60000
      request_line: 'GET / HTTP/1.0'
      healthy_threshold: 2
      response_timeout: 5000 
      reinitializing_timeout: 60000
  • tcp health check, for db service
    health_check:
      healthy_threshold: 2
      response_timeout: 2000
      port: ${db_port}
      unhealthy_threshold: 3
      initializing_timeout: 60000
      interval: 2000
      strategy: recreate
      reinitializing_timeout: 60000

Note: If your service take mora than 60 seconds to start, adjust accordingly initializing_timeout (ms) and reinitializing_timeout (ms) in healtcheck parameters.

Use variables with default values

To provide versatility, don't hardcode variables value. Use catalog questions with default values instead.

  • Map catalog question values to web service
    environment:
      DB_HOST: db
      DB_PORT: ${db_port}
      DB_NAME: ${db_name}
      DB_USER: ${db_user}
      DB_PASS: ${db_pass}
  • Map catalog question values to db service
    environment:
      DB_PORT: ${db_port}
      DB_NAME: ${db_name}
      DB_USER: ${db_user}
      DB_PASS: ${db_pass}
  • Add questions to rancher-compose
    - variable: db_name
      description: "db name to connect"
      label: "Db Name"
      required: true
      default: "demo"
      type: "string"
    - variable: db_user
      description: "db user to auth"
      label: "Db User"
      required: true
      default: "user"
      type: "string"
    - variable: db_pass
      description: "db pass to auth"
      label: "Db Password"
      required: true
      default: "pass"
      type: "password"

Use loadbalancers

Instead of exposing port directly from the service docker, use lb.

  • Add lb service
  lb:
    image: rancher/lb-service-haproxy:v0.6.4
    ports:
      - ${web_port}:${web_port}/tcp
      - ${db_port}:${db_port}/tcp
  • Configure lb service
  lb:
    scale: 1
    start_on_create: true
    lb_config:
      certs: []
      port_rules:
      - priority: 1
        protocol: http
        service: web
        source_port: ${web_port}
        target_port: 80
      - priority: 2
        protocol: tcp
        service: db
        source_port: ${db_port}
        target_port: ${db_port}
    health_check:
      healthy_threshold: 2
      response_timeout: 2000
      port: 42
      unhealthy_threshold: 3
      initializing_timeout: 60000
      interval: 2000
      reinitializing_timeout: 60000

Expose just needed ports

Do you really need to expose db port?? Probably not....

  • Redefine lb service
  lb:
    image: rancher/lb-service-haproxy:v0.6.4
    ports:
      - ${web_port}:${web_port}/tcp
  • Reconfigure lb service
  lb:
    scale: 1
    start_on_create: true
    lb_config:
      certs: []
      port_rules:
      - priority: 1
        protocol: http
        service: web
        source_port: ${web_port}
        target_port: 80
    health_check:
      healthy_threshold: 2
      response_timeout: 2000
      port: 42
      unhealthy_threshold: 3
      initializing_timeout: 60000
      interval: 2000
      reinitializing_timeout: 60000

Use volumes

Depending of the service, may need volumes to manage persistence properly. Package would be more versatile if you could choose different drivers for service volumes.

  • Add volume for web data
    volumes:
      - web_data:<PATH>
  • Add volume for db data
    volumes:
      - db_data:<PATH>
  • Add volumes definition with selectable driver
volumes:
  web_data:
    driver: ${volume_driver}
  db_data:
    driver: ${volume_driver}
    per_container: true

Note: By default, a volume will be shared between all service instances that use it. All hosts needs access. If every service instance needs its own volume, use volume param per_container: true.

  • Add volume driver question.
    - variable: volume_driver
      description: "Volume driver to use with this service"
      label: "Volume driver"
      required: true
      default: "local"
      type: enum
      options:
        - local
        - rancher-nfs
        - rancher-efs
        - rancher-ebs

Note: Recommended enum type to avoid typo.

Use go templating

Latests catalog version permits to use go templating in docker-compose.yml to allow dynamism in catalog packages.

Example feature: Reuse already created db packages and link service, instead of deploying db service.

Logic: if we set db_link variable, we configure our service to connect with an external db. If not, we deploy an internal database service and its volume.

  • Rename docker-compose.yml to docker-compose.yml.tpl

  • Add go templating logic for services

{{- if ne .Values.db_link ""}}
    external_links:
      - ${db_link}:db
{{- else}}
    links:
      - db:db
  db:
    image: <db_image>:<db_version>
    volumes:
      - db_data:<PATH>
    environment:
      DB_PORT: ${db_port}
      DB_NAME: ${db_name}
      DB_USER: ${db_user}
      DB_PASS: ${db_pass}
{{- end}}
  • Add go templating logic for db volume
{{- if eq .Values.db_link ""}} 
  db_data:
    driver: ${volume_driver}
    per_container: true
{{- end}}
  • Add db link question
    - variable: "db_link"
      description: |
        External db link
      label: "External db"
      default: ""
      required: false
      type: "service"

Final package

This is the final package definition with all improvements applied.

Definition

  • docker-compose.yml.tpl
version: '2'
services:
  web:
    image: <web_image>:<web_version>
    volumes:
      - web_data:<PATH>
    environment:
      DB_HOST: db
      DB_PORT: ${db_port}
      DB_NAME: ${db_name}
      DB_USER: ${db_user}
      DB_PASS: ${db_pass}
{{- if ne .Values.db_link ""}}
    external_links:
      - ${db_link}:db
{{- else}}
    links:
      - db:db
  db:
    image: <db_image>:<db_version>
    volumes:
      - db_data:<PATH>
    environment:
      DB_PORT: ${db_port}
      DB_NAME: ${db_name}
      DB_USER: ${db_user}
      DB_PASS: ${db_pass}
{{- end}}
  lb:
    image: rancher/lb-service-haproxy:v0.6.4
    ports:
      - ${web_port}:${web_port}/tcp
volumes:
  web_data:
    driver: ${volume_driver}
{{- if eq .Values.db_link ""}} 
  db_data:
    driver: ${volume_driver}
    per_container: true
{{- end}}
  • rancher-compose.yml
version: '2'
catalog:
  name: <name>
  version: <version>
  description: <description>
  uuid: <uuid>
  questions:    
    - variable: web_port
      description: "public port to access the web"
      label: "Web Port"
      required: true
      default: "80"
      type: "int"
    - variable: db_port
      description: "public port to access the db"
      label: "Db Port"
      required: true
      default: "3306"
      type: "int"
    - variable: db_name
      description: "db name to connect"
      label: "Db Name"
      required: true
      default: "demo"
      type: "string"
    - variable: db_user
      description: "db user to auth"
      label: "Db User"
      required: true
      default: "user"
      type: "string"
    - variable: db_pass
      description: "db pass to auth"
      label: "Db Password"
      required: true
      default: "pass"
      type: "password"
    - variable: volume_driver
      description: "Volume driver to use with this service"
      label: "Volume driver"
      required: true
      default: "local"
      type: enum
      options:
        - local
        - rancher-nfs
        - rancher-efs
        - rancher-ebs
    - variable: "db_link"
      description: |
        External db link
      label: "External db"
      default: ""
      required: false
      type: "service"
services:
  web:
    scale: 1
    retain_ip: true
    health_check:
      port: 80
      interval: 5000
      unhealthy_threshold: 3
      initializing_timeout: 60000
      request_line: 'GET / HTTP/1.0'
      healthy_threshold: 2
      response_timeout: 5000 
      reinitializing_timeout: 60000
  db:
    scale: 1
    retain_ip: true
    health_check:
      healthy_threshold: 2
      response_timeout: 2000
      port: ${db_port}
      unhealthy_threshold: 3
      initializing_timeout: 60000
      interval: 2000
      strategy: recreate
      reinitializing_timeout: 60000
  lb:
    scale: 1
    start_on_create: true
    lb_config:
      certs: []
      port_rules:
      - priority: 1
        protocol: http
        service: web
        source_port: ${web_port}
        target_port: 80
    health_check:
      healthy_threshold: 2
      response_timeout: 2000
      port: 42
      unhealthy_threshold: 3
      initializing_timeout: 60000
      interval: 2000
      reinitializing_timeout: 60000