Create an account


Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Fedora - Deploy Fedora CoreOS servers with Terraform

#1
Deploy Fedora CoreOS servers with Terraform

<div style="margin: 5px 5% 10px 5%;"><img src="https://www.sickgaming.net/blog/wp-content/uploads/2020/12/deploy-fedora-coreos-servers-with-terraform.png" width="693" height="458" title="" alt="" /></div><div><p>Fedora CoreOS is a lightweight, secure operating system optimized for running containerized workloads. A YAML document is all you need to describe the workload you’d like to run on a Fedora CoreOS server.</p>
<p>This is wonderful for a single server, but how would you describe a fleet of cooperating Fedora CoreOS servers? For example, what if you wanted a set of servers running load balancers, others running a database cluster and others running a web application? How can you get them all configured and provisioned? How can you configure them to communicate with each other? This article looks at how Terraform solves this problem.</p>
<p> <span id="more-32390"></span> </p>
<h2>Getting started</h2>
<p>Before you start, decide whether you need to review the basics of Fedora CoreOS. Check out this <a href="https://fedoramagazine.org/getting-started-with-fedora-coreos/">previous article</a> on the Fedora Magazine:</p>
<figure class="wp-block-embed-wordpress wp-block-embed is-type-wp-embed is-provider-fedora-magazine">
<div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="tFk8xcK0hL"><p><a href="https://fedoramagazine.org/getting-started-with-fedora-coreos/">Getting started with Fedora CoreOS</a></p></blockquote>
</div>
</figure>
<p><strong>Terraform</strong> is an open source tool for defining and provisioning infrastructure. Terraform defines infrastructure as code in files. It provisions infrastructure by calculating the difference between the desired state in code and observed state and applying changes to remove the difference.</p>
<p>HashiCorp, the company that created and maintains Terraform, offers an RPM repository to install Terraform. </p>
<pre class="wp-block-preformatted">sudo dnf config-manager --add-repo \ https://rpm.releases.hashicorp.com/fedor...icorp.repo
sudo dnf install terraform</pre>
<p>To get yourself familiar with the tools, start with a simple example. You’re going to create a single Fedora CoreOS server in AWS. To follow along, you need to install <em>awscli</em> and have an AWS account. <em>awscli</em> can be installed from the Fedora repositories and configured using the <em>aws configure</em> command</p>
<pre class="wp-block-preformatted">sudo dnf install -y awscli
aws configure</pre>
<p><em><strong>Please note, </strong>AWS is a paid service. If executed correctly, participants should expect less than $1 USD in charges, but mistakes may lead to unexpected charges</em>. </p>
<h2>Configuring Terraform</h2>
<p>In a new directory, create a file named <em>config.yaml</em>. This file will hold the contents of your Fedore CoreOS configuration. The configuration simply adds an SSH key for the <em>core</em> user. Modify the<strong><em> </em></strong><em>authorized_ssh_key</em> section to use your own.</p>
<div class="wp-block-group">
<div class="wp-block-group__inner-container">
<pre class="wp-block-preformatted">variant: fcos
version: 1.2.0
passwd: users: - name: core authorized_ssh_keys: - "ssh-ed25519 AAAAC3....... user@hostname"</pre>
<p>Next, create a file <em>main.tf</em> to contain your Terraform specification. Take a look at the contents section by section. It begins with a block to specify the versions of your providers.</p>
</div>
</div>
<pre class="wp-block-preformatted">terraform { required_providers { ct = { source = "poseidon/ct" version = "0.7.1" } aws = { source = "hashicorp/aws" version = "~&gt; 3.0" } }
}</pre>
<p>Terraform uses providers to control infrastructure. Here it uses the AWS provider to provision EC2 servers, but it can provision any kind of AWS infrastructure. The <a rel="noreferrer noopener" href="https://github.com/poseidon/terraform-provider-ct" target="_blank">ct provider</a> from <a rel="noreferrer noopener" href="https://www.psdn.io/" target="_blank">Poseidon Labs</a> stands for <em>config transpiler</em>. This provider will <a href="https://en.wikipedia.org/wiki/Source-to-source_compiler">transpile</a> Fedora CoreOS configurations into Ignition configurations. As a result, you do not need to use <em><a rel="noreferrer noopener" href="https://docs.fedoraproject.org/en-US/fedora-coreos/using-fcct/" target="_blank">fcct</a></em> to transpile your configurations. Now that your provider versions are specified, initialize them.</p>
<pre class="wp-block-preformatted">provider "aws" { region = "us-west-2"
} provider "ct" {}</pre>
<p>The AWS region is set to <em>us-west-2</em> and the <em>ct </em>provider requires no configuration. With the providers configured, you’re ready to define some infrastructure. Use a <em><a rel="noreferrer noopener" href="https://www.terraform.io/docs/configuration/data-sources.html" target="_blank">data source</a></em> block to read the configuration.</p>
<pre class="wp-block-preformatted">data "ct_config" "config" { content = file("config.yaml") strict = true
}</pre>
<p>With this data block defined, you can now access the transpiled Ignition output as <em>data.ct_config.config.rendered</em>. To create an EC2 server, use a <a rel="noreferrer noopener" href="https://www.terraform.io/docs/configuration/blocks/resources/syntax.html" target="_blank">resource</a> block, and pass the Ignition output as the <em>user_data</em> attribute.</p>
<pre class="wp-block-preformatted">resource "aws_instance" "server" { ami = "ami-0699a4456969d8650" instance_type = "t3.micro" user_data = data.ct_config.config.rendered
}</pre>
<p>This configuration hard-codes the virtual machine image (AMI) to the latest <em>stable</em> image of Fedora CoreOS in the <em>us-west-2</em> region at time of writing. If you would like to use a different region or stream, you can discover the correct AMI on the <a rel="noreferrer noopener" href="https://getfedora.org/en/coreos/download?tab=cloud_launchable&amp;stream=stable" target="_blank">Fedora CoreOS downloads page</a>.</p>
<p>Finally, you’d like to know the public IP address of the server once it’s created. Use an <em><a rel="noreferrer noopener" href="https://www.terraform.io/docs/configuration/outputs.html" target="_blank">output</a></em> block to define the outputs to be displayed once Terraform completes its provisioning.</p>
<pre id="block-5c4fc71c-60bb-49c4-a04e-fc67b2aff056" class="wp-block-preformatted">output "instance_ip_addr" { value = aws_instance.server.public_ip
}</pre>
<p>Alright! You’re ready to create some infrastructure. To deploy the server simply run:</p>
<pre class="wp-block-preformatted"><strong>terraform init</strong> # Installs the provider dependencies
<strong>terraform apply</strong> # Displays the proposed changes and applies them</pre>
<p>Once<em> </em>completed, Terraform prints the public IP address of the server, and you can SSH to the server by running <em>ssh core@{public ip here}</em>. Congratulations — you’ve provisioned your first Fedora CoreOS server using Terraform!</p>
<h2>Updates and immutability</h2>
<p>At this point you can modify the configuration in <em>config.yaml</em> however you like. To deploy your change simply run <em>terraform apply</em> again. Notice that each time you change the configuration, when you run <em>terraform apply</em> it destroys the server and creates a new one. This aligns well with the Fedora CoreOS philosophy: Configuration can only happen once. Want to change that configuration? Create a new server. This can feel pretty alien if you’re accustomed to provisioning your servers once and continuously re-configuring them with tools like <a rel="noreferrer noopener" href="https://www.ansible.com/" target="_blank">Ansible</a>, <a rel="noreferrer noopener" href="https://puppet.com/" target="_blank">Puppet</a> or <a rel="noreferrer noopener" href="https://www.chef.io/" target="_blank">Chef</a>.</p>
<p>The benefit of always creating new servers is that it is significantly easier to test that newly provisioned servers will act as expected. It can be much more difficult to account for all of the possible ways in which updating a system in place may break. Tooling that adheres to this philosophy typically falls under the heading of <em>Immutable Infrastructure</em>. This approach to infrastructure has some of the same benefits seen in functional programming techniques, namely that mutable state is often a source of error.</p>
<h2>Using variables</h2>
<p>You can use Terraform <a rel="noreferrer noopener" href="https://www.terraform.io/docs/configuration/variables.html" target="_blank">input variables</a> to parameterize your infrastructure. In the previous example, you might like to parameterize the AWS region or instance type. This would let you deploy several instances of the same configuration with differing parameters. What if you want to parameterize the Fedora CoreOS configuration? Do so using the <a rel="noreferrer noopener" href="https://www.terraform.io/docs/configuration/functions/templatefile.html" target="_blank"><em>templatefile</em></a> function.</p>
<p>As an example, try parameterizing the username of your user. To do this, add a <em>username</em> variable to the <em>main.tf</em> file:</p>
<pre class="wp-block-preformatted">variable "username" { type = string description = "Fedora CoreOS user" default = "core"
}</pre>
<p>Next, modify the <em>config.yaml</em> file to turn it into a template. When rendered, the <em>${username}</em> will be replaced.</p>
<pre class="wp-block-preformatted">variant: fcos
version: 1.2.0
passwd: users: - name: ${username} authorized_ssh_keys: - "ssh-ed25519 AAAAC3....... user@hostname"</pre>
<p>Finally, modify the data block to render the template using the <em>templatefile</em> function.</p>
<pre class="wp-block-preformatted">data "ct_config" "config" { content = templatefile( "config.yaml", { username = var.username } ) strict = true
}</pre>
<p>To deploy with <em>username</em> set to <em>jane</em>, run <em>terraform apply -var=”username=jane”</em>. To verify, try to SSH into the server with <em>ssh jane@{public ip address}</em>.</p>
<h2>Leveraging the dependency graph</h2>
<p>Passing variables from Terraform into Fedora CoreOS configuration is quite useful. But you can go one step further and pass infrastructure data into the server configuration. This is where Terraform and Fedora CoreOS start to really shine.</p>
<p>Terraform creates a <a rel="noreferrer noopener" href="https://www.terraform.io/docs/internals/graph.html" target="_blank">dependency graph</a> to model the state of infrastructure and to plan updates. If the output of one resource (e.g the public IP address of a server) is passed as the input of another service (e.g the destination in a firewall rule), Terraform understands that changes in the former require recreating or modifying the later. If you pass infrastructure data into a Fedora CoreOS configuration, it will participate in the dependency graph. Updates to the inputs will trigger creation of a new server with the new configuration.</p>
<p>Consider a system of one load balancer and three web servers as an example.</p>
<figure class="wp-block-image size-large"><img loading="lazy" width="693" height="458" src="https://www.sickgaming.net/blog/wp-content/uploads/2020/12/deploy-fedora-coreos-servers-with-terraform.png" alt="" class="wp-image-32437" /></figure>
<p>The goal is to configure the load balancer with the IP address of each web server so that it can forward traffic to them.</p>
<h2>Web server configuration</h2>
<p>First, create a file <em>web.yaml </em>and add a simple Nginx configuration with a templated message.</p>
<pre class="wp-block-preformatted">variant: fcos
version: 1.2.0
systemd: units: - name: nginx.service enabled: true contents: | [Unit] Description=Nginx Web Server After=network-online.target Wants=network-online.target [Service] ExecStartPre=-/bin/podman kill nginx ExecStartPre=-/bin/podman rm nginx ExecStartPre=/bin/podman pull nginx ExecStart=/bin/podman run --name nginx -p 80:80 -v /etc/nginx/index.html:/usr/share/nginx/html/index.html:z nginx [Install] WantedBy=multi-user.target
storage: directories: - path: /etc/nginx files: - path: /etc/nginx/index.html mode: 0444 contents: inline: | &lt;html&gt; &lt;h1&gt;Hello from Server ${count}&lt;/h1&gt; &lt;/html&gt;</pre>
<p>In <em>main.tf</em>, you can create three web servers using this template with the following blocks:</p>
<pre class="wp-block-preformatted">data "ct_config" "web" { count = 3 content = templatefile( "web.yaml", { count = count.index } ) strict = true
} resource "aws_instance" "web" { count = 3 ami = "ami-0699a4456969d8650" instance_type = "t3.micro" user_data = data.ct_config.web[count.index].rendered
}</pre>
<p>Notice the use of <em>count = 3</em> and the <em>count.index</em> variable. You can use <a rel="noreferrer noopener" href="https://www.terraform.io/docs/configuration/meta-arguments/count.html" target="_blank">count</a> to make many copies of a resource. Here, it creates three configurations and three web servers. The <em>count.index</em> variable is used to pass the first configuration to the first web server and so on.</p>
<h2>Load balancer configuration</h2>
<p>The load balancer will be a basic <a rel="noreferrer noopener" href="https://www.haproxy.org/" target="_blank">HAProxy load balancer</a> that forwards to each server. Place the configuration in a file named <em>lb.yaml</em>:</p>
<pre class="wp-block-preformatted">variant: fcos
version: 1.2.0
systemd: units: - name: haproxy.service enabled: true contents: | [Unit] Description=Haproxy Load Balancer After=network-online.target Wants=network-online.target [Service] ExecStartPre=-/bin/podman kill haproxy ExecStartPre=-/bin/podman rm haproxy ExecStartPre=/bin/podman pull haproxy ExecStart=/bin/podman run --name haproxy -p 80:8080 -v /etc/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro haproxy [Install] WantedBy=multi-user.target
storage: directories: - path: /etc/haproxy files: - path: /etc/haproxy/haproxy.cfg mode: 0444 contents: inline: | global log stdout format raw local0 defaults mode tcp log global option tcplog frontend http bind *:8080 default_backend http backend http balance roundrobin
%{ for name, addr in servers ~} server ${name} ${addr}:80 check
%{ endfor ~}</pre>
<p>The template expects a <a rel="noreferrer noopener" href="https://www.terraform.io/docs/configuration/expressions/types.html#maps-objects" target="_blank">map</a> with server names as keys and IP addresses as values. You can create that using the <a rel="noreferrer noopener" href="https://www.terraform.io/docs/configuration/functions/zipmap.html" target="_blank"><em>zipmap</em></a> function. Use the ID of the web servers as keys and the public IP addresses as values.</p>
<pre class="wp-block-preformatted">data "ct_config" "lb" { content = templatefile( "lb.yaml", { servers = zipmap( aws_instance.web.*.id, aws_instance.web.*.public_ip ) } ) strict = true
} resource "aws_instance" "lb" { ami = "ami-0699a4456969d8650" instance_type = "t3.micro user_data = data.ct_config.lb.rendered
}</pre>
<p>Finally, add an output block to display the IP address of the load balancer.</p>
<pre class="wp-block-preformatted">output "load_balancer_ip" { value = aws_instance.lb.public_ip
}</pre>
<p>All right! Run <em>terraform apply </em>and the IP address of the load balancer displays on completion. You should be able to make requests to the load balancer and get responses from each web server.</p>
<pre class="wp-block-preformatted">$ <strong>export LB={{load balancer IP here}}</strong>
$ <strong>curl $LB</strong>
&lt;html&gt; &lt;h1&gt;Hello from Server 0&lt;/h1&gt;
&lt;/html&gt;
$ <strong>curl $LB</strong>
&lt;html&gt; &lt;h1&gt;Hello from Server 1&lt;/h1&gt;
&lt;/html&gt;
$ <strong>curl $LB</strong>
&lt;html&gt; &lt;h1&gt;Hello from Server 2&lt;/h1&gt;
&lt;/html&gt;</pre>
<p>Now you can modify the configuration of the web servers or load balancer. Any changes can be realized by running <em>terraform apply</em> once again. Note in particular that any change to the web server IP addresses will cause Terraform to recreate the load balancer (changing the count from 3 to 4 is a simple test). Hopefully this emphasizes that the load balancer configuration is indeed a part of the Terraform dependency graph.</p>
<h2>Clean up</h2>
<p>You can destroy all the infrastructure using the <em>terraform destroy </em>command. Simply navigate to the folder where you created <em>main.tf</em> and run <em>terraform destroy</em>.</p>
<h2>Where next?</h2>
<p>Code for this tutorial can be found at <a href="https://github.com/nsmith5/fcos-terraform-tutorial">this GitHub repository</a>. Feel free to play with examples and contribute more if you find something you’d love to share with the world. To learn more about all the amazing things Fedora CoreOS can do, dive into <a href="https://docs.fedoraproject.org/en-US/fedora-coreos/getting-started/" target="_blank" rel="noreferrer noopener">the docs</a> or come chat with <a href="https://discussion.fedoraproject.org/c/server/coreos/5" target="_blank" rel="noreferrer noopener">the community</a>. To learn more about Terraform, you can rummage through <a href="https://www.terraform.io/docs/index.html" target="_blank" rel="noreferrer noopener">the docs</a>, checkout #terraform on <a href="https://freenode.net/" target="_blank" rel="noreferrer noopener">freenode</a>, or contribute on <a href="https://github.com/hashicorp/terraform" target="_blank" rel="noreferrer noopener">GitHub</a>.</p>
</div>


https://www.sickgaming.net/blog/2020/12/...terraform/
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

Forum software by © MyBB Theme © iAndrew 2016