
Hybrid public/private cloud
Seamless
Companies often do not exclusively use public cloud services such as Amazon Web Services (AWS) [1], Microsoft Azure [2], or Google Cloud [3]. Instead, they rely on a mix, known as a hybrid cloud. In this scenario, you connect your data centers (private cloud) with the resources of a public cloud provider. The term "private cloud" is somewhat misleading, in that the operation of many data centers has little to do with cloud-based working methods, but it looks like the name is here to stay.
The advantage of a hybrid cloud is that companies can use it to absorb peak loads or special requirements without having to procure new hardware for five- or six-digit figures.
In this article, I show how you can add a cloud extension to an Ansible [4] role that addresses local servers. To do this, you extend a local Playbook for an Elasticsearch cluster so that it can also be used in the cloud, and the resources disappear again after use.
Cloudy Servers
In classical data center operation, a server is typically used for a project and installed by an admin. It then runs through a life cycle in which it receives regular patches. At some point, it is no longer needed or is outdated. In the virtualized world, the same thing happens in principle, only with virtual servers. However, for performance reasons, you no longer necessarily retire them. With a few commands or clicks, you can simply assign more and faster resources.
Things are different in the cloud, where you have a service in mind. To operate it, you have to provide defined resources for a certain period of time, build these services in an automated process, to the extent possible (sometimes even from scratch), use them, and only pay the public cloud providers for the period of use. Then, you shut down the machines, reducing resource requirements to zero.
If these resources include virtual machines (VMs), you again build them automatically, use them, and delete them. The classic server life cycle is therefore irrelevant and is degraded to a component in an architecture that an admin brings to life at the push of a button.
Visible for Everyone?
One popular misconception about the use of public cloud services is that these services are "freely accessible on the Internet." This statement is not entirely true, because most cloud providers leave it to the admin to decide whether to provide a service or a VM with a publicly accessible IP address. Additionally, you usually have to activate explicitly all the services you want to be accessible from outside, although this usually does not apply to the services required for administration – that is, Secure Shell (SSH) for Linux VMs and the Remote Desktop Protocol (RDP) for Windows VMs. By way of an example, when an AWS admin picks a database from the Database-as-a-Service offerings, they can only access it through the IP address they use to control the AWS Console.
If you set up the virtual networks in the public cloud with private addresses only, they are just as invisible from the Internet as the servers in your own data center.
Cloudbnb
At AWS, but also in the Google and Microsoft clouds, for example, the concept of the virtual private cloud (VPC) acts as the account's backbone. With an AWS account in each region, you can even operate several VPC instances side by side.
To connect to this network, the cloud providers offer a site-to-site VPN service. Alternatively, you can set up your own VPN gateway (e.g., in the form of a VM, such as Linux with IPsec/OpenVPN) or a virtual firewall appliance, the latter of which offers a higher level of security, but usually comes at a price.
This service ultimately creates a structure, that, conceptually, does not differ fundamentally from the way in which you would connect branch offices to the head office – with one difference: The public cloud provider can potentially access the data on the machines and in the containers.
Protecting Data
The second major security concern relates to storing data. Especially when processing personal information for members of the European Union (EU), you have to be careful for legal reasons about which of the cloud provider's regions is used to store the data. Relocating the customer database to Japan might turn out to be a less than brilliant idea. Even if the data is stored on servers within the EU, the question of who gets access still needs to be clarified.
Encrypting data in AWS is possible [5]. If you do not have confidence in your abilities, you could equip a Linux VM with a self-encrypted volume (e.g., LUKS [6]) and not store the password on the server. With AWS, this does not work for system disks, but it does at least for data volumes. After starting the VM, you have to send the password. This process can be automated from your own data center. The only possible route of access for the provider is to read the machine RAM; this risk exists where modern hardware enables live encryption, as well.
As a last resort, you can ensure that the computing resources in the cloud only access data managed by the local data center. However, you will need a powerful Internet connection.
Solving a Problem
Assume you have a local Elasticsearch cluster of three nodes: a master node, which also houses Logstash and Kibana, and two data nodes with data on board (Figure 1).

You now want to provide this cluster temporarily two more data nodes in the public cloud. You could have several reasons for this; for example, you might want to replace the physical data nodes because of hardware problems, or you might temporarily need higher performance for data analysis. Because it is not typically worthwhile to procure new hardware on a temporary basis, the public cloud is a solution. The logic is shown in Figure 1; the machines started there must become part of the Elastic cluster.
The following explanations assume you have already written Ansible roles for installing the Elasticsearch-Logstash-Kibana (ELK) cluster. You will find a listing for a Playbook on the ADMIN FTP site [7]. Thanks to the structure of these roles, you can add more nodes by appending parameters to the Hosts file, and it includes installing the software on the node.
The roles that Ansible calls are determined by the Hosts file (usually in /etc/ansible/hosts) and the variables set in it for each host. Listing 1 shows the original file.
Listing 1: ELK Stack Hosts File
10.0.2.25 ansible_ssh_user=root logstash=1 kibana=1 masternode=1 grafana=1 do_ela=1 10.0.2.26 ansible_ssh_user=root masternode=0 do_ela=1 10.0.2.44 ansible_ssh_user=root masternode=0 do_ela=1
Host 10.0.2.25 is the master node on which all software runs. The other two hosts are the data nodes of the cluster. The variable do_ela controls whether the Elasticsearch role can perform installations. When expanding the cluster, this ensures that Ansible does not reconfigure the existing nodes – but more about the details later.
Extending the Cluster in AWS
The virtual infrastructure in AWS comprises a VPC with two subnets. One subnet can be reached from the Internet; the other represents the internal area, which also contains the two servers on which data nodes 3 and 4 are to run. In between is a virtual firewall, by Fortinet in this case, that terminates the VPN tunnel and controls access with firewall rules.
This setup requires several configuration steps in AWS: You need to create the VPC with a main network. On this, you then assign all the subnets: one internal (inside) and one accessible from the Internet (outside). Then, you create an Internet gateway in the outside subnet. Through this, the data traffic migrating toward the Internet finds an exit from the cloud. For this purpose, you define a routing table for the outside subnet that specifies this Internet gateway as the standard route (Figure 2).

Cloud Firewall
In the next step, you create a security group that comprises host-related firewall rules for AWS. Because the firewall can protect itself, the group opens the firewall for all incoming and outgoing traffic, although this could be restricted. The next step is to create an S3 bucket that contains the starting configuration and the license for the firewall. Next, you generate the config file for the firewall and upload it with the license. For a rented, but more expensive, firewall, this license information can also be omitted.
Now set up network interfaces for the firewall in the two subnets. Also, create a role that later allows the firewall instance to access the S3 bucket. Assign the network interfaces and the role to the firewall instance to be created, and link the subnets to the firewall. Create a routing table for the inside subnet and specify the firewall network card responsible for the inside network as the target; then, generate a public IP address and assign it to the outside network interface.
The next step is to set up a security group for the servers. To do this, first create two server instances on the inside subnet and change the inside firewall interface from a DHCP client to the static IP address that the AWS firewall has currently assigned to the card. Now set up a VPN tunnel from the local network into the AWS cloud. You need to define the rules and routes on the firewall on the local network. At the end of this configuration marathon, and assuming that all the new cloud servers can be reached, finally install and configure the Elastic stack on the two new AWS servers (Figure 3).

Cloud Shaping
In principle, Ansible would be able to perform all these tasks, but that would cause problems when cleaning up the ensemble in the cloud, at the latest. You would either have to save the information of the components created there locally, or you would have to search the Playbook for the components to be removed before the Playbook cleans them up.
A stack (similar to OpenStack) in which you describe the complete infrastructure, which can be parameterized in YAML or JSON format, is easier. Then, you build the stack with a call (also using Ansible) and clear it up with another call. The proprietary AWS technology for this is known as CloudFormation.
CloudFormation lets the stack receive a construction parameter: in this example, the IP addresses of the networks in the VPC. The author of the stack can also enter a return value, which is typically the external IP address of a generated object, so that the user of the stack knows how to use the cloud service.
Most VM images in AWS use cloud-init technology (please see the "cloud-init" box). Because CloudFormation can also provide cloud-init data to a VM, where do you draw the line between Ansible and CloudFormation? Where it is practicable and reduces the total overhead.
Fixed and Variable
The fixed components of the target infrastructure are the VMs (the firewall and the two servers for Elastic), the network structure, the routing structure, and the logic of the security groups. All of this information should definitely be included in the CloudFormation template.
The network's IP addresses, the AWS region, and the names of the objects are variable and used as parameters in the stack definition; you have to specify them when calling the stack. The variables also include the name of the S3 bucket for the cloud-init configuration of the firewall and the public SSH key stored with AWS, which is used to enable access to the Linux VMs.
Finally, you need the internal IP addresses of the Linux VMs, the external public IP address of the firewall, and the internal private IP address of the firewall for further configuration. Accordingly, these addresses pertain to the return values of the stack.
Ansible does all the work. It fills the variables, generates the firewall configuration, which the AWS firewall receives via cloud-init, and installs the software on the Linux VMs. Cloud-init could also install the software, but Ansible will set up exactly the roles that helped to configure the local servers at the beginning.
I developed the CloudFormation template from the version by firewall manufacturer Fortinet [8]. I simplified the structure, compared with their version on GitHub, so that the template in the cloud only raises a firewall and not a cluster. Additionally, the authors of the Fortinet template used a Lambda function to modify the firewall configuration. Here, this task is done by the Playbook, which in turn uses the template.
In the CloudFormation template, the process can be static. The two Linux VMs use CentOS as their operating system and should run on the internal subnet; you simply attach them to the template and the return values. Listings 2 through 4 show excerpts from the stack definition in YAML format. The complete YAML file can be downloaded from the ADMIN anonymous FTP site [7].
Listing 2: YAML Stack Definition Part 1
01 [...] 02 Resources: 03 FortiVPC: 04 Type: AWS::EC2::VPC 05 Properties: 06 CidrBlock: 07 Ref: VPCNet 08 Tags: 09 - Key: Name 10 Value: 11 Ref: VPCName 12 13 FortiVPCFrontNet: 14 Type: AWS::EC2::Subnet 15 Properties: 16 CidrBlock: 17 Ref: VPCSubnetFront 18 MapPublicIpOnLaunch: true 19 VpcId: 20 Ref: FortiVPC 21 22 FortiVPCBackNet: 23 Type: AWS::EC2::Subnet 24 Properties: 25 CidrBlock: 26 Ref: VPCSubnetBack 27 MapPublicIpOnLaunch: false 28 AvailabilityZone: !GetAtt FortiVPCFrontNet.AvailabilityZone 29 VpcId: 30 Ref: FortiVPC 31 32 FortiSecGroup: 33 Type: AWS::EC2::SecurityGroup 34 Properties: 35 GroupDescription: Group for FG 36 GroupName: fg 37 SecurityGroupEgress: 38 - IpProtocol: -1 39 CidrIp: 0.0.0.0/0 40 SecurityGroupIngress: 41 - IpProtocol: tcp 42 FromPort: 0 43 ToPort: 65535 44 CidrIp: 0.0.0.0/0 45 - IpProtocol: udp 46 FromPort: 0 47 ToPort: 65535 48 CidrIp: 0.0.0.0/0 49 VpcId: 50 Ref: FortiVPC 51 52 InstanceProfile: 53 Properties: 54 Path: / 55 Roles: 56 - Ref: InstanceRole 57 Type: AWS::IAM::InstanceProfile 58 InstanceRole: 59 Properties: 60 AssumeRolePolicyDocument: 61 Statement: 62 - Action: 63 - sts:AssumeRole 64 Effect: Allow 65 Principal: 66 Service: 67 - ec2.amazonaws.com 68 Version: 2012-10-17 69 Path: / 70 Policies: 71 - PolicyDocument: 72 Statement: 73 - Action: 74 - ec2:Describe* 75 - ec2:AssociateAddress 76 - ec2:AssignPrivateIpAddresses 77 - ec2:UnassignPrivateIpAddresses 78 - ec2:ReplaceRoute 79 - s3:GetObject 80 Effect: Allow 81 Resource: '*' 82 Version: 2012-10-17 83 PolicyName: ApplicationPolicy 84 Type: AWS::IAM::Role
The objects of the AWS::EC2::Instance type are the VMs designed to extend the Elastic stack (Listings 3 and 4). Because of the firewall, the VM is more complex to configure; it has to have two dedicated interface objects so that routing can point to it (Listing 3, line 11).
Listing 3: YAML Stack Definition Part 2
01 FortiInstance:
02 Type: "AWS::EC2::Instance"
03 Properties:
04 IamInstanceProfile:
05 Ref: InstanceProfile
06 ImageId: "ami-06f4dce9c3ae2c504" # for eu-west-3 paris
07 InstanceType: t2.small
08 AvailabilityZone: !GetAtt FortiVPCFrontNet.AvailabilityZone
09 KeyName:
10 Ref: KeyName
11 NetworkInterfaces:
12 - DeviceIndex: 0
13 NetworkInterfaceId:
14 Ref: fgteni1
15 - DeviceIndex: 1
16 NetworkInterfaceId:
17 Ref: fgteni2
18 UserData:
19 Fn::Base64:
20 Fn::Join:
21 - ''
22 -
23 - "{\n"
24 - '"bucket"'
25 - ' : "'
26 - Ref: S3Bucketname
27 - '"'
28 - ",\n"
29 - '"region"'
30
31 - ' : '
32 - '"'
33 - Ref: S3Region
34 - '"'
35 - ",\n"
36 - '"license"'
37 - ' : '
38 - '"'
39 - /
40 - Ref: LicenseFileName
41 - '"'
42 - ",\n"
43 - '"config"'
44 - ' : '
45 - '"'
46 - /fg.txt
47 - '"'
48 - "\n"
49 - '}'
50
51 InternetGateway:
52 Type: AWS::EC2::InternetGateway
53
54 AttachGateway:
55 Properties:
56 InternetGatewayId:
57 Ref: InternetGateway
58 VpcId:
59 Ref: FortiVPC
60 Type: AWS::EC2::VPCGatewayAttachment
61
62 RouteTablePub:
63 Type: AWS::EC2::RouteTable
64 Properties:
65 VpcId:
66 Ref: FortiVPC
67
68 DefRoutePub:
69 DependsOn: AttachGateway
70 Properties:
71 DestinationCidrBlock: 0.0.0.0/0
72 GatewayId:
73 Ref: InternetGateway
74 RouteTableId:
75 Ref: RouteTablePub
76 Type: AWS::EC2::Route
77
78 RouteTablePriv:
79 [...]
80
81 DefRoutePriv:
82 [...]
83
84 SubnetRouteTableAssociationPub:
85 Properties:
86 RouteTableId:
87 Ref: RouteTablePub
88 SubnetId:
89 Ref: FortiVPCFrontNet
90 Type: AWS::EC2::SubnetRouteTableAssociation
91
92 SubnetRouteTableAssociationPriv:
93 [...]
Importantly, the firewall instance and both generated interfaces are located in the same availability zone; otherwise, the stack will fail. To this end, the VMs contain descriptions, and the second subnet contains the reference to the availability zone of the first subnet.
The UserData part of the firewall instance (Listing 3, line 18) contains a description file that tells the VM where to find the configuration and license file previously uploaded by Ansible.
The network configuration has already been described and is defined at the top of Listing 2. The finished template can now be run at the command line with the
aws cloudformation create-stack
Listing 4: YAML Stack Definition Part 3
01 [...] 02 ServerInstance: 03 Type: "AWS::EC2::Instance" 04 Properties: 05 ImageId: "ami-0e1ab783dc9489f34" # Centos7 for paris 06 InstanceType: t3.2xlarge 07 AvailabilityZone: !GetAtt FortiVPCFrontNet.AvailabilityZone 08 KeyName: 09 Ref: KeyName 10 SubnetId: 11 Ref: FortiVPCBackNet 12 SecurityGroupIds: 13 - !Ref ServerSecGroup 14 15 Server2Instance: 16 Type: "AWS::EC2::Instance" 17 Properties: 18 ImageId: "ami-0e1ab783dc9489f34" # Centos7 for paris 19 [...]
command, which specifies the name of the YAML file created and fills the parameters at the beginning of the stack. The S3 bucket you want to pass in must already exist. Both the license and the generated configuration should be uploaded up front. All these tasks are done by the Ansible Playbook, as shown in Listings 5 through 9.
The Playbook uses multiple "plays." The first (Listing 5) creates the configuration for the firewall and, if not available, the S3 bucket (line 20) as described and uploads it together with the license.
Listing 5: Ansible Playbook Part 1
01 ---
02 - name: Create VDC in AWS with fortigate as front
03 hosts: localhost
04 connection: local
05 gather_facts: no
06 vars:
07 region: eu-west-3
08 licensefile: license.lic
09 wholenet: 10.100.0.0/16
10 frontnet: 10.100.254.0/28
11 netmaskback: 17
12 backnet: "10.100.0.0/{{ netmaskback }}"
13 lnet: 10.0.2.0/24
14 rnet: "{{ backnet }}"
15 s3name: stackdata
16 keyname: mgtkey
17 fgtpw: Firewall-Passwort
18
19 tasks:
20 - name: Create S3 Bucket for data
21 aws_s3:
22 bucket: "{{ s3name }}"
23 region: "{{ region }}"
24 mode: create
25 permission: public-read
26 register: s3bucket
27
28 - name: Upload License
29 aws_s3:
30 bucket: "{{ s3name }}"
31 region: "{{ region }}"
32 overwrite: different
33 object: "/{{ licensefile }}"
34 src: "{{ licensefile }}"
35 mode: put
36
37 - name: Generate Config
38 template:
39 src: awsforti-template.conf.j2
40 dest: fg.txt
41
42 - name: Upload Config
43 aws_s3:
44 bucket: "{{ s3name }}"
45 region: "{{ region }}"
46 overwrite: different
47 object: "/fg.txt"
48 src: "fg.txt"
49 mode: put
50 [...]
The next task creates the complete stack (Listing 6). What's new is the connection to the old Elasticsearch Playbook or Hosts file. The latter has a group named elahosts, which adds the IP addresses of the two new servers to the Playbook so that a total of five hosts are in the list for further execution of the Playbook. However, some operations will only take place on the new hosts. Listing 6 (lines 44 and 49) creates the newhosts group, to which it adds the two hosts.
Listing 6: Ansible Playbook Part 2
01 [...]
02 - name: Create Stack
03 cloudformation:
04 stack_name: VPCFG
05 state: present
06 region: "{{ region }}"
07 template: fortistack.yml
08 template_parameters:
09 InstanceType: c5.large
10 FGUserName: admin
11 KeyName: "{{ keyname }}"
12 VPCName: VDCVPC
13 VPCNet: "{{ wholenet }}"
14 Kubnet: "{{ lnet }}"
15 VPCSubnetFront: "{{ frontnet }}"
16 VPCSubnetBack: "{{ backnet }}"
17 S3Bucketname: "{{ s3name }}"
18 LicenseFileName: "{{ licensefile }}"
19 S3Region: "{{ region }}"
20 register: stackinfo
21
22 - name: Print Results
23 [...]
24
25 - name: Wait for VM to be up
26 [...]
27
28 - name: New Group
29 add_host:
30 groupname: fg
31 hostname: "{{ stackinfo.stack_outputs.FortiGatepubIp }}"
32
33 - name: Add ElaGroup1
34 add_host:
35 groupname: elahosts
36 hostname: "{{ stackinfo.stack_outputs.Server1Address }}"
37
38 - name: Add ElaGroup2
39 add_host:
40 groupname: elahosts
41 hostname: "{{ stackinfo.stack_outputs.Server2Address }}"
42
43 - name: Add NewGroup1
44 add_host:
45 groupname: newhosts
46 hostname: "{{ stackinfo.stack_outputs.Server1Address }}"
47
48 - name: Add NewGroup2
49 add_host:
50 groupname: newhosts
51 hostname: "{{ stackinfo.stack_outputs.Server2Address }}"
52
53 - name: Set Fact
54 set_fact:
55 netmaskback: "{{ netmaskback }}"
56
57 - name: Set Fact
58 set_fact:
59 fgtpw: "{{ fgtpw }}"
60 [...]
The next play (Listing 7) configures the firewall. In its existing configuration, the static IP address for the inside network card is missing – AWS only sets this when creating the instance. Because the data is now known, the Playbook can define the IP address.
Listing 7: Ansible Playbook Part 3
01 [...]
02 - name: ChangePW
03 hosts: fg
04 vars:
05 ansible_user: admin
06 ansible_ssh_common_args: -o StrictHostKeyChecking=no
07 ansible_ssh_pass: "{{ hostvars['localhost'].stackinfo.stack_outputs.FortiGateId }}"
08 gather_facts: no
09
10 tasks:
11 - raw: |
12 "{{ hostvars['localhost'].fgtpw }}"
13 "{{ hostvars['localhost'].fgtpw }}"
14 config system interface
15 edit port2
16 set mode static
17 set ip "{{ hostvars['localhost'].stackinfo.stack_outputs.FGIntAddress }}/17"
18 next
19 end
20 tags: pw
21 - name: Wait for License Reboot
22 pause:
23 minutes: 1
24
25 - name: Wait for VM to be up
26 wait_for:
27 host: "{{ inventory_hostname }}"
28 port: 22
29 state: started
30 delegate_to: localhost
31 [...]
When logging in to the firewall for the first time, the firewall requires a password change. You can use several methods to set up Fortigate in Ansible. However, the FortiOS network modules that have been included in the Ansible distribution for a while do not yet work properly. The raw approach is used here (Listing 7, line 10), which pushes the commands onto the device, as on the command line.
The first two lines of the raw task set the password, which resides on the instance ID in the AWS version. Because the license has already been installed, the firewall reboots after installation. At the end, the Ansible script in Listing 7 waits for the reboot to occur and then for it to reach the firewall again.
A play now follows that teaches the local firewall what the VPN tunnel to the firewall looks like in AWS (Listing 8). The VPN definition at the other end was in the previously uploaded configuration. Because of the described problems with the Ansible modules for FortiOS (I suspect incompatibilities between Ansible modules and the Python fosapi), the play uses Ansible's URI method to configure the firewall. Authentication for the API requires a login process; it then returns a token that is used in the following REST calls.
Listing 8: Ansible Playbook Part 4
01 [...]
02 - name: Local Firewall Config
03 hosts: localhost
04 connection: local
05 gather_facts: no
06 vars:
07 localfw: 10.0.2.90
08 localadmin: admin
09 localpw: ""
10 vdom: root
11 lnet: 10.0.2.0/24
12 rnet: 10.100.0.0/17
13 remotefw: "{{ stackinfo.stack_outputs.FortiGatepubIp }}"
14 localinterface: port1
15 psk: "<Password>"
16 vpnname: elavpn
17
18 tasks:
19
20 - name: Get the token with uri
21 uri:
22 url: https://{{ localfw }}/logincheck
23 method: POST
24 validate_certs: no
25 body: "ajax=1&username={{ localadmin }}&password={{ localpw }}"
26 register: uriresult
27 tags: gettoken
28
29 - name: Get Token out
30 set_fact:
31 token: "{{
32 uriresult.cookies['ccsrftoken'] | regex_replace('\"', '') }}"
33
34 - debug: msg="{{ token }}"
35
36 - name: Phase1 old Style
37 uri:
38 url: https://{{ localfw }}/api/v2/cmdb/vpn.ipsec/phase1-interface
39 validate_certs: no
40 method: POST
41 headers:
42 X-CSRFTOKEN: "{{ token }}"
43 Cookie: "{{ uriresult.set_cookie }}"
44 body: "{{ lookup('template', 'forti-phase1.j2') }}"
45 body_format: json
46 register: answer
47 tags: phase1
48
49 - name: Phase2 old style
50 uri:
51 url: https://{{ localfw }}/api/v2/cmdb/vpn.ipsec/phase2-interface
52 validate_certs: no
53 method: POST
54 headers:
55 X-CSRFTOKEN: "{{ token }}"
56 Cookie: "{{ uriresult.set_cookie }}"
57 body: "{{ lookup('template', 'forti-phase2.j2') }}"
58 body_format: json
59 register: answer
60 tags: phase2
61
62 - name: Route old style
63 [...]
64
65 - name: Local Object Old Style
66 [...]
67
68 - name: Remote Object Old Stlye
69 [...]
70
71 - name: FW-Rule-In old style
72 uri:
73 url: https://{{ localfw }}/api/v2/cmdb/firewall/policy
74 validate_certs: no
75 method: POST
76 headers:
77 Cookie: "{{ uriresult.set_cookie }}"
78 X-CSRFTOKEN: "{{ token }}"
79 body:
80 [...]
81 body_format: json
82 register: answer
83 tags: rulein
84
85 - name: FW-Rule-out old style
86 uri:
87 url: https://{{ localfw }}/api/v2/cmdb/firewall/policy
88 validate_certs: no
89 method: POST
90 headers:
91 Cookie: "{{ uriresult.set_cookie }}"
92 X-CSRFTOKEN: "{{ token }}"
93 body:
94 [...]
95 body_format: json
96 register: answer
97 tags: ruleout
98 [...]
The configuration initially consists of the key exchange phase1 and phase2 parameters. The phase1 parameter contains the password, crypto parameters, and IP address of the firewall in AWS. The phase2 parameter also provides crypto parameters and data for the local and remote networks. The configuration also provides a route (line 62) that passes the network on the AWS side to the VPN tunnel, and two firewall rules that allow traffic from and to the private network on the AWS side (lines 71 and 85).
A bit further down (Listing 9), the Playbook sets the do_ela parameter to 1 for the new hosts so that this role will also install Elasticsearch later. It uses 0 as the value for masternode, because the new hosts are data nodes. Because it usually takes some time for the VPN connection to be ready for use, the play now waits for the master node of the Elastic cluster until it can reach a new node via SSH.
Listing 9: Ansible Playbook Part 5
01 [...]
02 - name: Set Facts for new hosts
03 hosts: newhosts
04 [...]
05 masternode: 0
06 do_ela: 1
07
08 - name: Wait For VPN Tunnel
09 hosts: 10.0.2.25
10 [...]
11
12 - name: Install elastic
13 hosts: elahosts
14 vars:
15 elaversion: 6
16 eladatapath: /elkdata
17 ansible_ssh_common_args: -o StrictHostKeyChecking=no
18
19 tasks:
20
21 - ini_file:
22 path: /etc/yum.conf
23 section: main
24 option: ip_resolve
25 value: 4
26 become: yes
27 become_method: sudo
28 when: do_ela == 1
29 name: Change yum.conf
30
31 - yum:
32 name: "*"
33 state: "latest"
34 name: RHUpdates
35 become: yes
36 become_method: sudo
37 when: do_ela == 1
38
39 - include_role:
40 name: matrix.centos-elasticcluster
41 vars:
42 clustername: matrixlog
43 elaversion: 6
44 when: do_ela == 1
45
46 - name: Set Permissions for data
47 file:
48 path: "{{ eladatapath }}"
49 owner: elasticsearch
50 group: elasticsearch
51 state: directory
52 mode: "4750"
53 become: yes
54 become_method: sudo
55 when: do_ela == 1
56
57 - systemd:
58 name: elasticsearch
59 state: restarted
60 become: yes
61 become_method: sudo
62 when: do_ela == 1
The last piece of the Playbook finally installs Elasticsearch on the new node and adapts its configuration to match the existing cluster. The role takes the major version of Elasticsearch as a parameter and a path in which the Elasticsearch server can store the data, which allows you to insert a separate mount point on a data-only disk.
Within AWS, all systems are prepared for IPv6, but this does not apply to the configuration used here. Therefore, the first task forces you to switch to IPv4. The second one updates the configuration of the system. In the third task, the Elastic cluster role finally installs and configures the software.
Because Ansible only creates the Elasticsearch user to which the elkdata/ folder should belong during the installation, the script also has to tweak the permissions and restart Elasticsearch (starting in line 46). This completes the cloud expansion. If everything worked out, the Kibana console will be presented with the view from Figure 4 after a few moments.

Big Cleanup
If you want to remove the extension, you have to remove the nodes from the cluster with an API call:
curl -X PUT 10.0.2.25:9200/_cluster/settings -H 'Content-Type: application/json' -d '{"transient" : {"cluster.routing.allocation.exclude._ip":"10.100.68.139" } }'
This command blocks further assignments and causes the cluster to move all shards away from this node. After the action, no more shards are assigned, and you can simply switch off the node.
Conclusion
The hybrid cloud thrives, because admins can transfer scripts and Playbooks seamlessly from their environment to the cloud world. Although higher quality cloud services exist than those covered in this article (AWS also has Elasticsearch as a Service), these services typically have only limited suitability for direct docking. To use them, you would have to take into account configuring the peculiarities of the respective cloud provider. With VMs in Microsoft Azure, however, the example shown here would work directly, so the user would only have to replace the CloudFormation part with an Azure template.
