Monthly Archives: October 2019

AWS – Install Kubernetes using Ansible

Introduction

This blog post is a follow up of a post we wrote couple of weeks ago where we created 3 EC2 instances on AWS. That blog post can be found here. If you want to follow along with this guide, then use that post to create 3 droplets.

Executing the code from that post, will result in three EC2 instances on AWS.

Using Ansible to install Kubernetes

In this post, we will focus on using Ansible to install Kubernetes. We will base ourselves on an excellent post written by the DigitalOcean community but finetune it for our AWS endeavour. Also, what we do here with Ansible are essentially the same steps as I did in this post where I configured Kubernetes manually.

First of all, let’s create a hosts file for our Ansible scripts. We will define 1 master and 2 workers. The IP addresses are the same as the ones in the DigitalOcean screenshot (obviously).

#hosts
[masters]
master ansible_host=63.35.233.16 ansible_user=ubuntu

[workers]
worker1 ansible_host=52.211.207.166 ansible_user=ubuntu
worker2 ansible_host=34.243.84.7 ansible_user=ubuntu

[all:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_private_key_file=~/.ssh/AWS-Cisco.pem

Next, we will define create a file to install all the updates, to create the ubuntu user and ensure the ubuntu user has sudo rights.

- hosts: all
  become: yes
  tasks:
    - name: create the 'ubuntu' user
      user: name=ubuntu append=yes state=present createhome=yes shell=/bin/bash

    - name: allow 'ubuntu' to have passwordless sudo
      lineinfile:
        dest: /etc/sudoers
        line: 'ubuntu ALL=(ALL) NOPASSWD: ALL'
        validate: 'visudo -cf %s'

    - name: set up authorized keys for the ubuntu user
      authorized_key: user=ubuntu key="{{item}}"
      with_file:
        - ~/.ssh/id_rsa.pub

 

Next up, we will create a different file that installs the Kubernetes specific dependencies on the 3 nodes. First of all, we start with the installation of Docker, then we add Kubernetes as well as the kubelet and kubeadm toolset.

#kube-dependencies.yml
- hosts: all
  become: yes
  tasks:
   - name: install Docker
     apt:
       name: docker.io
       state: present
       update_cache: true

   - name: install APT Transport HTTPS
     apt:
       name: apt-transport-https
       state: present

   - name: add Kubernetes apt-key
     apt_key:
       url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
       state: present

   - name: add Kubernetes' APT repository
     apt_repository:
      repo: deb http://apt.kubernetes.io/ kubernetes-xenial main
      state: present
      filename: 'kubernetes'

   - name: install kubelet
     apt:
       name: kubelet
       state: present
       update_cache: true

   - name: install kubeadm
     apt:
       name: kubeadm
       state: present

- hosts: master
  become: yes
  tasks:
   - name: install kubectl
     apt:
       name: kubectl
       state: present
When the above is finished, we will create a specific file for the master node. This file will take care of the initialization of the Kubernetes cluster, will create the .kube directory and will copy the admin.conf file to the user’s kube.config file and also install the flannel network. Just similar as what we did in the manual process.
#master.yml
- hosts: master
  become: yes
  tasks:
    - name: initialize the cluster
      shell: kubeadm init --pod-network-cidr=10.244.0.0/16 >> cluster_initialized.txt
      args:
        chdir: $HOME
        creates: cluster_initialized.txt

    - name: create .kube directory
      become: yes
      become_user: ubuntu
      file:
        path: $HOME/.kube
        state: directory
        mode: 0755

    - name: copy admin.conf to user's kube config
      copy:
        src: /etc/kubernetes/admin.conf
        dest: /home/ubuntu/.kube/config
        remote_src: yes
        owner: ubuntu

    - name: install Pod network
      become: yes
      become_user: ubuntu
      shell: kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml >> pod_network_setup.txt
      args:
        chdir: $HOME
        creates: pod_network_setup.txt
Note: if you prefer to use Calico CNI plugin instead of Flannel, it suffices to replace that line with:
    - name: install Pod network
      become: yes
      become_user: ubuntu
      kubectl apply -f kubectl apply -f >> pod_network_setup.txt
      args:
        chdir: $HOME
        creates: pod_network_setup.txt

Last file we create is the workers.yml file. In that file, we will first retrieve the join command from the master node and then we will join worker nodes to the cluster.

- hosts: master
  become: yes
  gather_facts: false
  tasks:
    - name: get join command
      shell: kubeadm token create --print-join-command
      register: join_command_raw

    - name: set join command
      set_fact:
        join_command: "{{ join_command_raw.stdout_lines[0] }}"

- hosts: workers
  become: yes
  tasks:
    - name: join cluster
      shell: "{{ hostvars['master'].join_command }} >> node_joined.txt"
      args:
        chdir: $HOME
        creates: node_joined.txt

Executing the Ansible playbooks

First, we will perform the initial configuration as described above.
WAUTERW-M-65P7:Ansible-K8S-AWS wauterw$ ansible-playbook -i hosts initial.yml

PLAY [all] ****************************************************************************************************

TASK [Gathering Facts] ****************************************************************************************
ok: [master]
ok: [worker1]
ok: [worker2]

TASK [create the 'ubuntu' user] *******************************************************************************
ok: [master]
ok: [worker2]
ok: [worker1]

TASK [allow 'ubuntu' to have passwordless sudo] ***************************************************************
ok: [master]
ok: [worker2]
ok: [worker1]

TASK [set up authorized keys for the ubuntu user] *************************************************************
ok: [worker2] => (item=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6ULleEc9D1kKNcHV/Xf06RNSqbHTFvbX79WqK3F6I6DG8rcogw+WGw0pH7lqg/8k1aWjH+apNXjl8NO3ABzLNyc3I6ytUvFOHIkRZeEPNuH4mpb122VAPTYiSpU+F6E8PQUomnGPSSMwfU+T0Sq33H4TwXkgRnuuhD/ZutJjb31mARluxU/nf2FlKuLKHuImc39opoMF wauterw@WAUTERW-M-65P7)
ok: [worker1] => (item=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6ULsdRMZUYAdxPrtdQgAXSMJEHye9ea8e7E/U275c9D1kKNcHV/Xf06RNSqbHTFvbX79WqK3F6I6DG8rcogw+WGw0pH7lqg/8k1aWjH+apNXjl8NO3ABzLNyc3I6ytUvFOHIkRZeEPNuH4mpb122VAPTYiSpU+F6E8PQUomnGPSSMwfU+T0Sq33H4TwXkgRnuuhD/ZutJjb31mARluxU/nf2FlKuLKHuImc39opoMF wauterw@WAUTERW-M-65P7)
ok: [master] => (item=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAE8PQUomnGPSSMwfU+T0Sq33H4TwXkgRnuuhD/ZutJjb31mARluxU/nf2FlKuLKHuImc39opoMF wauterw@WAUTERW-M-65P7)

PLAY RECAP ****************************************************************************************************
master                     : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker1                    : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker2                    : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

(base) WAUTERW-M-65P7:Ansible-K8S-AWS wauterw$ 

Next, we will install the Kubernetes dependencies:

(base) WAUTERW-M-65P7:Ansible-K8S-AWS wauterw$ ansible-playbook -i hosts kube-dependencies.yml 

PLAY [all] ************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************
ok: [worker2]
ok: [worker1]
ok: [master]

TASK [install Docker] *************************************************************************************************************
 [WARNING]: Could not find aptitude. Using apt-get instead

changed: [master]
changed: [worker2]
changed: [worker1]

TASK [install APT Transport HTTPS] ************************************************************************************************
changed: [worker2]
changed: [worker1]
changed: [master]

TASK [add Kubernetes apt-key] *****************************************************************************************************
changed: [worker2]
changed: [worker1]
changed: [master]

TASK [add Kubernetes' APT repository] *********************************************************************************************
changed: [master]
changed: [worker2]
changed: [worker1]

TASK [install kubelet] ************************************************************************************************************
changed: [master]
changed: [worker2]
changed: [worker1]

TASK [install kubeadm] ************************************************************************************************************
changed: [worker2]
changed: [worker1]
changed: [master]

PLAY [master] *********************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************
ok: [master]

TASK [install kubectl] ************************************************************************************************************
ok: [master]

PLAY RECAP ************************************************************************************************************************
master                     : ok=9    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker1                    : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker2                    : ok=7    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

When the dependencies are installed, it’s time to configure the master.

(base) WAUTERW-M-65P7:Ansible-K8S-AWS wauterw$ ansible-playbook -i hosts master.yml 

PLAY [master] *********************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************
ok: [master]

TASK [initialize the cluster] *****************************************************************************************************
changed: [master]

TASK [create .kube directory] *****************************************************************************************************
changed: [master]

TASK [copy admin.conf to user's kube config] **************************************************************************************
changed: [master]

TASK [install Pod network] ********************************************************************************************************
changed: [master]

PLAY RECAP ************************************************************************************************************************
master                     : ok=5    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Next up, we are going to configure the workers.
(base) WAUTERW-M-65P7:Ansible-K8S-AWS wauterw$ ansible-playbook -i hosts workers.yml 

PLAY [master] *********************************************************************************************************************

TASK [get join command] ***********************************************************************************************************
changed: [master]

TASK [set join command] ***********************************************************************************************************
ok: [master]

PLAY [workers] ********************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************
ok: [worker2]
ok: [worker1]

TASK [join cluster] ***************************************************************************************************************
changed: [worker1]
changed: [worker2]

PLAY RECAP ************************************************************************************************************************
master                     : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker1                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
worker2                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Validate the K8S cluster

First, we need to SSH into the K8S master. In our case, this is available on ‘63.35.233.16’.
WAUTERW-M-65P7:Ansible-K8S-AWS wauterw$ ssh ubuntu@63.35.233.16
This works, since we have configured the EC2 instances with our public key that is stored in our ~/.ssh directory (id_rsa,pub). Since we are also using our security key from AWS, we can also login as follows.
WAUTERW-M-65P7:Keys_and_Certificates wauterw$ ssh -i AWS.pem ubuntu@63.35.233.16
Next, let’s see if our nodes are up:
ubuntu@ip-172-31-13-72:~$ kubectl get nodes -o wide
NAME               STATUS   ROLES    AGE     VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION    CONTAINER-RUNTIME
ip-172-31-12-102   Ready       2m30s   v1.16.2   172.31.12.102           Ubuntu 18.04.1 LTS   4.15.0-1021-aws   docker://18.9.7
ip-172-31-13-72    Ready    master   6m32s   v1.16.2   172.31.13.72            Ubuntu 18.04.1 LTS   4.15.0-1021-aws   docker://18.9.7
ip-172-31-15-214   Ready       2m17s   v1.16.2   172.31.15.214           Ubuntu 18.04.1 LTS   4.15.0-1021-aws   docker://18.9.7
And our namespaces:
ubuntu@ip-172-31-13-72:~$ kubectl get namespaces
NAME              STATUS   AGE
default           Active   11m
kube-node-lease   Active   11m
kube-public       Active   11m
kube-system       Active   11m
In the next blogs, we will deploy some applications onto our cluster. See you next time!

AWS – Create Ubuntu server using Terraform

Introduction

The post you’re about to read is pretty similar to an earlier post you might have come across. At that time, I was in need for a Kubernetes cluster on DigitalOcean. Today, I need a Kubernetes cluster on AWS for professional reasons. Why not re-use the work we have done before….

Getting Started

Ensure you have Terraform installed on your system. This is pretty straightforward so I’ll not cover the installation procedure in this post. We’ll focus instead on the files we need to write.

Create a file called ‘provider.tf’. In fact, the name does not matter, but as this file contains the AWS Terraform provider it certainly is not a bad choice as filename

provider "aws" {
  access_key = "${var.ec2_aws_access_key}"
  secret_key = "${var.ec2_aws_secret_key}"
  region     = "${var.ec2_region}"
}

If you need to know why we provide these attributes, have a look at the Terraform AWS provider documentation.

Next, create a file called ‘main.tf’ (again the name does not matter)

resource "aws_instance" "OneServer" {
    ami             =       "${var.ec2_image}"
    instance_type   =       "${var.ec2_instance_type}"
    count           =       "${var.ec2_numberofservers}"
    key_name        =       "${var.ec2_keypair}"
    tags = {
        Name        =       "${var.ec2_tags}-${count.index+1}"
    }
}

Here we tell Terraform which OS we need (we will go for Ubuntu), which instance type we require and how many servers we want to create.

Note that we are using variables here. Therefore, we also need to be able to pass these variables. Let’s create a variables.tf file.

variable "ec2_aws_access_key" {
  default = "AK***GK"
}

variable "ec2_aws_secret_key" {
  default = "rHbE***dCPhKC"
}

variable "ec2_region" {
  default = "eu-west-1"
}

variable "ec2_image" {
  default = "ami-00035f41c82244dab"
}

variable "ec2_instance_type" {
  default = "t2.medium"
}

variable "ec2_tags" {
  default = "Kubernetes"
}

variable "ec2_numberofservers" {
  default = 3
}

variable "ec2_keypair" {
  default = "AWS"
}

The above file contains all variables that are required for our Terraform files.

Executing Terraform

(base) WAUTERW-M-65P7:Create_3_UbuntuServers_K8S wauterw$ terraform init

Initializing the backend...

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 2.32"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Next, perform a ‘terraform plan’ as follows.

(base) WAUTERW-M-65P7:Create_3_UbuntuServers_K8S wauterw$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.OneServer[0] will be created
  + resource "aws_instance" "OneServer" {
      + ami                          = "ami-00035f41c82244dab"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.medium"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = "AWS-Cisco"
      + network_interface_id         = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Kubernetes-1"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_instance.OneServer[1] will be created
  + resource "aws_instance" "OneServer" {
      + ami                          = "ami-00035f41c82244dab"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.medium"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = "AWS-Cisco"
      + network_interface_id         = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Kubernetes-2"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_instance.OneServer[2] will be created
  + resource "aws_instance" "OneServer" {
      + ami                          = "ami-00035f41c82244dab"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.medium"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = "AWS-Cisco"
      + network_interface_id         = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Kubernetes-3"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

So we can see that the plan is to add three resources, essentially the EC2 instances themselves

As a next step, we will be deploying the servers by performing a ‘terraform apply’ command.

(base) WAUTERW-M-65P7:Create_3_UbuntuServers_K8S wauterw$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.OneServer[0] will be created
  + resource "aws_instance" "OneServer" {
      + ami                          = "ami-00035f41c82244dab"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.medium"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = "AWS-Cisco"
      + network_interface_id         = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Kubernetes-1"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_instance.OneServer[1] will be created
  + resource "aws_instance" "OneServer" {
      + ami                          = "ami-00035f41c82244dab"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.medium"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = "AWS-Cisco"
      + network_interface_id         = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Kubernetes-2"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_instance.OneServer[2] will be created
  + resource "aws_instance" "OneServer" {
      + ami                          = "ami-00035f41c82244dab"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.medium"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = "AWS-Cisco"
      + network_interface_id         = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tags                         = {
          + "Name" = "Kubernetes-3"
        }
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_instance.OneServer[0]: Creating...
aws_instance.OneServer[2]: Creating...
aws_instance.OneServer[1]: Creating...
aws_instance.OneServer[0]: Still creating... [10s elapsed]
aws_instance.OneServer[1]: Still creating... [10s elapsed]
aws_instance.OneServer[2]: Still creating... [10s elapsed]
aws_instance.OneServer[2]: Still creating... [20s elapsed]
aws_instance.OneServer[0]: Still creating... [20s elapsed]
aws_instance.OneServer[1]: Still creating... [20s elapsed]
aws_instance.OneServer[1]: Still creating... [30s elapsed]
aws_instance.OneServer[0]: Still creating... [30s elapsed]
aws_instance.OneServer[2]: Still creating... [30s elapsed]
aws_instance.OneServer[1]: Creation complete after 31s [id=i-03718b505cd5bbd89]
aws_instance.OneServer[0]: Creation complete after 39s [id=i-0c98fe42adf9e172a]
aws_instance.OneServer[2]: Still creating... [40s elapsed]
aws_instance.OneServer[2]: Creation complete after 49s [id=i-0b907bc8179a20e7d]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

On AWS, we see the EC2 instances appearing.

SSH into the server

Let’s now try to ssh into the server by using our keypair. Go to the local folder on your PC/MAC and locate the public key that corresponds to the SSH key you uploaded to the AWS webinterface

(base) WAUTERW-M-65P7:Keys_and_Certificates wauterw$ ssh -i AWS.pem ubuntu@176.34.153.183
Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-1021-aws x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Wed Oct 16 19:07:15 UTC 2019

  System load:  0.0               Processes:           97
  Usage of /:   13.3% of 7.69GB   Users logged in:     0
  Memory usage: 4%                IP address for eth0: 172.31.36.71
  Swap usage:   0%

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.

ubuntu@ip-172-31-36-71:~$

Deleting the server

Deleting the server is very easy with Terraform. Just use the ‘Terraform destroy’ command will do the trick.


Done!