Terraform guide 2 - Containers

Terraform: Containers

Terraform and Docker

Managing Docker networks

  • environment: mkdir -p ~/terraform/docker/networks
  • example
    1. Crate new files required
      1
      touch {variables.tf,image.tf,network.tf,main.tf}
    2. Edit variables.tf
      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
      variable "mysql_root_password" {
      description = "The MySQL root password."
      default = "P4sSw0rd0!"
      }

      variable "ghost_db_username" {
      description = "Ghost blog database username."
      default = "root"
      }

      variable "ghost_db_name" {
      description = "Ghost blog database name."
      default = "ghost"
      }

      variable "mysql_network_alias" {
      description = "The network alias for MySQL."
      default = "db"
      }

      variable "ghost_network_alias" {
      description = "The network alias for Ghost"
      default = "ghost"
      }

      variable "ext_port" {
      description = "Public port for Ghost"
      default = "8080"
      }
    3. Edit image.tf
      1
      2
      3
      4
      5
      6
      7
      resource "docker_image" "ghost_image" {
      name = "ghost:alpine"
      }

      resource "docker_image" "mysql_image" {
      name = "mysql:5.7"
      }
    4. Edit network.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      resource "docker_network" "public_bridge_network" {
      name = "public_ghost_network"
      driver = "bridge"


      resource "docker_network" "private_bridge_network" {
      name = "ghost_mysql_internal"
      driver = "bridge"
      internal = true
      }
    5. Edit main.tf
      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
      41
      42
      43
      44
      resource "docker_container" "mysql_container" {
      name = "ghost_database"
      image = "${docker_image.mysql_image.name}"
      env = [
      "MYSQL_ROOT_PASSWORD=${var.mysql_root_password}"
      ]
      networks_advanced {
      name = "${docker_network.private_bridge_network.name}"
      aliases = ["${var.mysql_network_alias}"]
      }
      }

      # do not deploy ghost until you have the db ready
      resource "null_resource" "sleep" {
      depends_on = ["docker_container.mysql_container"]
      provisioner "local-exec" {
      command = "sleep 15s"
      }
      }

      resource "docker_container" "blog_container" {
      name = "ghost_blog"
      image = "${docker_image.ghost_image.name}"
      depends_on = ["null_resource.sleep", "docker_container.mysql_container"]
      env = [
      "database__client=mysql",
      "database__connection__host=${var.mysql_network_alias}",
      "database__connection__user=${var.ghost_db_username}",
      "database__connection__password=${var.mysql_root_password}",
      "database__connection__database=${var.ghost_db_name}"
      ]
      ports {
      internal = "2368"
      external = "${var.ext_port}"
      }
      networks_advanced {
      name = "${docker_network.public_bridge_network.name}"
      aliases = ["${var.ghost_network_alias}"]
      }
      networks_advanced {
      name = "${docker_network.private_bridge_network.name}"
      aliases = ["${var.ghost_network_alias}"]
      }
      }
    6. Work with Terraform
      1
      2
      3
      4
      5
      6
      7
      terraform init
      terraform validate
      terraform plan -out=tfplan -var 'ext_port=8082'
      terraform apply tfplan

      # clean up
      terraform destroy -auto-approve -var 'ext_port=8082'

Managing Docker volumes

  • environment
    1
    2
    cp -r ~/terraform/docker/networks ~/terraform/docker/volumes
    cd ../volumes/
  • example
    1. Edit volumes.tf
      1
      2
      3
      resource "docker_volume" "mysql_data_volume" {
      name = "mysql_data"
      }
    2. Edit main.tf
      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
      41
      42
      43
      44
      45
      46
      47
      resource "docker_container" "mysql_container" {
      name = "ghost_database"
      image = "${docker_image.mysql_image.name}"
      env = [
      "MYSQL_ROOT_PASSWORD=${var.mysql_root_password}"
      ]
      volumes {
      volume_name = "${docker_volume.mysql_data_volume.name}"
      container_path = "/var/lib/mysql"
      }
      networks_advanced {
      name = "${docker_network.private_bridge_network.name}"
      aliases = ["${var.mysql_network_alias}"]
      }
      }

      resource "null_resource" "sleep" {
      depends_on = ["docker_container.mysql_container"]
      provisioner "local-exec" {
      command = "sleep 15s"
      }
      }

      resource "docker_container" "blog_container" {
      name = "ghost_blog"
      image = "${docker_image.ghost_image.name}"
      depends_on = ["null_resource.sleep", "docker_container.mysql_container"]
      env = [
      "database__client=mysql",
      "database__connection__host=${var.mysql_network_alias}",
      "database__connection__user=${var.ghost_db_username}",
      "database__connection__password=${var.mysql_root_password}",
      "database__connection__database=${var.ghost_db_name}"
      ]
      ports {
      internal = "2368"
      external = "${var.ext_port}"
      }
      networks_advanced {
      name = "${docker_network.public_bridge_network.name}"
      aliases = ["${var.ghost_network_alias}"]
      }
      networks_advanced {
      name = "${docker_network.private_bridge_network.name}"
      aliases = ["${var.ghost_network_alias}"]
      }
      }
    3. Work with Terraform
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      terraform init
      terraform validate
      terraform plan -out=tfplan -var 'ext_port=8082'
      terraform apply tfplan
      # list Docker volumes
      docker volume inspect mysql_data
      # list the data in mysql_data
      sudo ls /var/lib/docker/volumes/mysql_data/_data

      # clean up
      terraform destroy -auto-approve -var 'ext_port=8082'

Creating swarm services

  • environment
    1
    2
    cp -r volumes/ services
    cd services
  • example
    1. Edit variables.tf
      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
      variable "mysql_root_password" {
      description = "The MySQL root password."
      default = "P4sSw0rd0!"
      }

      variable "ghost_db_username" {
      description = "Ghost blog database username."
      default = "root"
      }

      variable "ghost_db_name" {
      description = "Ghost blog database name."
      default = "ghost"
      }

      variable "mysql_network_alias" {
      description = "The network alias for MySQL."
      default = "db"
      }

      variable "ghost_network_alias" {
      description = "The network alias for Ghost"
      default = "ghost"
      }

      variable "ext_port" {
      description = "The public port for Ghost"
      }
    2. Edit images.tf
      1
      2
      3
      4
      5
      6
      7
      resource "docker_image" "ghost_image" {
      name = "ghost:alpine"
      }

      resource "docker_image" "mysql_image" {
      name = "mysql:5.7"
      }
    3. Edit network.tf
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      resource "docker_network" "public_bridge_network" {
      name = "public_network"
      driver = "overlay"
      }

      resource "docker_network" "private_bridge_network" {
      name = "mysql_internal"
      driver = "overlay"
      internal = true
      }
    4. Edit volumes.tf
      1
      2
      3
      resource "docker_volume" "mysql_data_volume" {
      name = "mysql_data"
      }
    5. Edit main.tf
      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
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      # you do not face issues for having the blog defined before db
      # as a swarm, they are on different machines
      resource "docker_service" "ghost-service" {
      name = "ghost"

      task_spec {
      container_spec {
      image = "${docker_image.ghost_image.name}"
      env {
      database__client = "mysql"
      database__connection__host = "${var.mysql_network_alias}"
      database__connection__user = "${var.ghost_db_username}"
      database__connection__password = "${var.mysql_root_password}"
      database__connection__database = "${var.ghost_db_name}"
      }
      }
      networks = [
      "${docker_network.public_bridge_network.name}",
      "${docker_network.private_bridge_network.name}"
      ]
      }

      endpoint_spec {
      ports {
      target_port = "2368"
      published_port = "${var.ext_port}"
      }
      }
      }

      resource "docker_service" "mysql-service" {
      name = "${var.mysql_network_alias}"
      task_spec {
      container_spec {
      image = "${docker_image.mysql_image.name}"
      env {
      MYSQL_ROOT_PASSWORD = "${var.mysql_root_password}"
      }

      # volumes as mounts
      mounts = [
      {
      target = "/var/lib/mysql"
      source = "${docker_volume.mysql_data_volume.name}"
      type = "volume"
      }
      ]
      }
      networks = ["${docker_network.private_bridge_network.name}"]
      }
      }
    6. Work with Terraform
      1
      2
      3
      4
      5
      6
      7
      8
      9
          terraform init
      terraform validate
      terraform plan -out=tfplan -var 'ext_port=8082'
      terraform apply tfplan
      docker service ls
      docker container ls

      s # clean up
      terraform destroy -auto-approve -var 'ext_port=8082'

Using secrets

  • store sensitive data, encrypted with Base64
  • environment: secrets
  • example
    1. Encode the password with Base64

      1
      echo 'p4sSWoRd0!' | base64
    2. Edit variables.tf

      1
      2
      3
      4
      5
      6
      7
      variable "mysql_root_password" {
      default = "cDRzU1dvUmQwIQo="
      }

      variable "mysql_db_password" {
      default = "cDRzU1dvUmQwIQo="
      }
    3. Create ``image.tf

      1
      2
      3
      resource "docker_image" "mysql_image" {
      name = "mysql:5.7"
      }
    4. Edit secrets.tf

      1
      2
      3
      4
      5
      6
      7
      8
      9
      resource "docker_secret" "mysql_root_password" {
      name = "root_password"
      data = "${var.mysql_root_password}"
      }

      resource "docker_secret" "mysql_db_password" {
      name = "db_password"
      data = "${var.mysql_db_password}"
      }
    5. Edit networks.tf

      1
      2
      3
      4
      5
      resource "docker_network" "private_overlay_network" {
      name = "mysql_internal"
      driver = "overlay"
      internal = true
      }
    6. Edit volumes.tf

      1
      2
      3
      resource "docker_volume" "mysql_data_volume" {
      name = "mysql_data"
      }
    7. Edit main.tf

      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
      resource "docker_service" "mysql-service" {
      name = "mysql_db"

      task_spec {
      container_spec {
      image = "${docker_image.mysql_image.name}"

      secrets = [
      {
      secret_id = "${docker_secret.mysql_root_password.id}"
      secret_name = "${docker_secret.mysql_root_password.name}"
      file_name = "/run/secrets/${docker_secret.mysql_root_password.name}"
      },
      {
      secret_id = "${docker_secret.mysql_db_password.id}"
      secret_name = "${docker_secret.mysql_db_password.name}"
      file_name = "/run/secrets/${docker_secret.mysql_db_password.name}"
      }
      ]

      env {
      MYSQL_ROOT_PASSWORD_FILE = "/run/secrets/${docker_secret.mysql_root_password.name}"
      MYSQL_DATABASE = "mydb"
      MYSQL_PASSWORD_FILE = "/run/secrets/${docker_secret.mysql_db_password.name}"
      }

      mounts = [
      {
      target = "/var/lib/mysql"
      source = "${docker_volume.mysql_data_volume.name}"
      type = "volume"
      }
      ]
      }
      networks = [
      "${docker_network.private_overlay_network.name}"
      ]
      }
      }
    8. Work with Terraform

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      terraform init
      terraform validate
      terraform plan -out=tfplan
      terraform apply tfplan
      # find the MySQL container
      docker container ls
      # log into the MySQL container
      docker container exec -it [CONTAINER_ID] /bin/bash
      # access MySQL
      mysql -u root -p

      # clean up
      terraform destroy -auto-approve

Terraform and Kubernetes

Setting up Kubernetes master and installing Terraform

  1. Edit kube-config.yml
    1
    2
    3
    4
    5
    6
    7
    8
    apiVersion: kubeadm.k8s.io/v1beta1
    kind: ClusterConfiguration
    kubernetesVersion: "v1.13.5"
    networking:
    podSubnet: 10.244.0.0/16
    apiServer:
    extraArgs:
    service-node-port-range: 8000-31274
  2. Initialize Kubernetes
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    sudo kubeadm init --config kube-config.yml
    # copy admin.conf to your home directory
    mkdir -p $HOME/.kube
    sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config
    # install Flannel
    sudo kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

    # untaint the Kubernetes Master
    kubectl taint nodes --all node-role.kubernetes.io/master-
  3. Install Terraform 0.11.13 on the Swarm manager
    1
    2
    3
    4
    sudo curl -O https://releases.hashicorp.com/terraform/0.11.13/terraform_0.11.13_linux_amd64.zip
    sudo unzip terraform_0.11.13_linux_amd64.zip -d /usr/local/bin/
    # check it
    terraform version

Creating a pod

  • environment: ~/terraform/pod
  • example
    1. Edit main.tf

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
        resource "kubernetes_pod" "ghost_alpine" {
      metadata {
      name = "ghost-alpine"
      }

      spec {
      host_network = "true"
      container {
      image = "ghost:alpine"
      name = "ghost-alpine"
      }
      }
      }
    2. Work with Terraform

      1
      2
      3
      4
      5
      6
      7
      8
      9
      terraform init
      terraform validate
      terraform plan
      terraform apply -auto-approve
      # check pods
      kubectl get pods

      # clean up
      terraform destroy -auto-approve

Creating a pod and service

  • environment ~/terraform/service
  • example
    1. Edit main.tf
      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
      resource "kubernetes_service" "ghost_service" {
      metadata {
      name = "ghost-service"
      }
      spec {
      selector {
      app = "${kubernetes_pod.ghost_alpine.metadata.0.labels.app}"
      }
      port {
      port = "2368"
      target_port = "2368"
      node_port = "8081"
      }
      type = "NodePort"
      }
      }

      resource "kubernetes_pod" "ghost_alpine" {
      metadata {
      name = "ghost-alpine"
      labels {
      app = "ghost-blog"
      }
      }

      spec {
      container {
      image = "ghost:alpine"
      name = "ghost-alpine"
      port {
      container_port = "2368"
      }
      }
      }
      }
    2. Initialize Terraform
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      terraform init
      terraform validate
      terraform plan
      terraform apply -auto-approve

      # check it
      kubectl get pods
      kubectl get services

      # clean up
      terraform destroy -auto-approve

Creating a deployment

  • environment: ~/terraform/deployment
  • example
    1. Edit main.tf
      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
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      resource "kubernetes_service" "ghost_service" {
      metadata {
      name = "ghost-service"
      }
      spec {
      selector {
      app = "${kubernetes_deployment.ghost_deployment.spec.0.template.0.metadata.0.labels.app}"
      }
      port {
      port = "2368"
      target_port = "2368"
      node_port = "8080"
      }

      type = "NodePort"
      }
      }

      resource "kubernetes_deployment" "ghost_deployment" {
      metadata {
      name = "ghost-blog"
      }

      spec {
      replicas = "1"

      selector {
      match_labels {
      app = "ghost-blog"
      }
      }

      template {
      metadata {
      labels {
      app = "ghost-blog"
      }
      }

      spec {
      container {
      name = "ghost"
      image = "ghost:alpine"
      port {
      container_port = "2368"
      }
      }
      }
      }
      }
      }
    2. Working with Terraform
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      terraform init
      terraform validate
      terraform plan
      terraform apply -auto-approve

      # check it
      kubectl get deployments
      kubectl get pods
      kubectl delete pod [POD_ID]

      # clean up
      terraform destroy -auto-approve