One place for hosting & domains

      Module

      How to Use Nginx’s map Module on Ubuntu 20.04


      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Nginx’s map module lets you create variables in Nginx’s configuration file whose values are conditional — that is, they depend on other variables’ values. In this guide, we will look at how to use Nginx’s map module to implement two examples: setting up a list of redirects from old website URLs to new ones and creating an allowlist of countries to control traffic to your website.

      Prerequisites

      To follow this tutorial, you will need:

      Step 1 — Creating and Testing an Example Webpage

      First, we will create a test file representing a newly published website. We’ll use this file to test our configuration.

      Let’s create a simple page, index.html, in the default Nginx website directory. This file will just have plain text describing what’s inside: Home:

      • sudo sh -c 'echo "Home" > /var/www/html/index.html'

      With this test file in place, we’ll check that it’s being served correctly with curl. We don’t need to specify index.html for this command because that file is served by default if no exact filename is provided:

      In response, you should see a single word saying Home just like below:

      Output

      Home

      Now let’s try to access a file that doesn’t exist in /var/www/html/, like old.html:

      • curl -L http://localhost/old.html

      The response will be a system error message, 404 Not Found, meaning the page does not exist:

      Output

      <html> <head><title>404 Not Found</title></head> <body bgcolor="white"> <center><h1>404 Not Found</h1></center> <hr><center>nginx/1.18.0 (Ubuntu)</center> </body> </html>

      We’re just using a dummy website in this tutorial, but if old.html was a page on a real website that used to exist and was deleted, returning a 404 would mean that all links to that page are broken. This is less than ideal because those links may have been indexed by Google, printed out or written down, or shared by any other means.

      In the next step, we’ll use the map module to make sure this old address will work again by redirecting viewers to the new replacements automatically.

      Step 2 — Configuring the Redirects

      For small websites with only a few pages, simple if conditional statements can be used for redirects and similar things. However, such a configuration is not easy to maintain or extend in the long run as the list of conditions grows longer.

      The map module is a more elegant, concise solution. It allows you to compare Nginx variable values against a list of conditions and then associate a new value with the variable depending on the match. In this example, we’ll be comparing the requested URL with the list of old pages that we want to redirect to their new counterparts. For each old address, we’ll associate the new one.

      The map module is a core Nginx module, which means it doesn’t need to be installed separately. To create the necessary map and redirect configuration, open the default server block Nginx configuration file in nano or your favorite text editor:

      • sudo nano /etc/nginx/sites-available/default

      Find the server configuration block, which looks like this:

      /etc/nginx/sites-available/default

      . . .
      # Default server configuration
      #
      
      server {
          listen 80 default_server;
          listen [::]:80 default_server;
      
      . . .
      

      We’ll be adding two new sections: one before the server block, and one inside it.

      The section before the server block is a new map block, which defines the mapping between the old URLs and the new ones using the map module. The section inside the server block is the redirect itself:

      /etc/nginx/sites-available/default

      . . .
      # Default server configuration
      #
      
      # Old website redirect map
      #
      map $uri $new_uri {
          /old.html /index.html;
      }
      
      server {
          listen 80 default_server;
          listen [::]:80 default_server;
      
          # Old website redirect
          if ($new_uri) {
              rewrite ^ $new_uri permanent;
          }
      . . .
      

      The map $uri $new_uri directive takes the contents of system $uri variable, which contains the URL address of the requested page, and then compares it against the list of conditions in the curly brackets. Each item in the list of conditions has two sections: the value to match against and the new value to assign to the variable if it matches.

      The line /old.html /index.html inside the map block means that if $uri’s value is /old.html, $new_uri will be changed to /index.html. If it doesn’t match, it’s not changed. Here, we only define one condition, but you can define as many conditions as you want in a map.

      Using a conditional if statement inside the server block, we check if the $new_uri variable’s value is set. If it is, it means the condition in the map was satisfied, and we should redirect to the new website using the rewrite command. The permanent keyword ensured that the redirect will be a 301 Moved Permanently HTTP redirect, which means that the old address is no longer valid and will not come back online.

      Save and close the file to exit.

      To enable the new configuration, restart Nginx:

      • sudo systemctl restart nginx

      To test the new configuration, execute the same request as before:

      • curl -L http://localhost/old.html

      This time there will be no 404 Not Found error in the output. Instead, you’ll see the simple home page we created in Step 1:

      Output

      Home

      This means the map has been appropriately configured, and you can use it to redirect URLs by adding more entries to the map.

      Redirecting URLs is one useful application of the map module. Another, which we’ll explore in the next step, is filtering traffic based on the visitors’ geographical location.

      Step 3 — Restricting Website Access to Certain Countries

      Sometimes, a server might receive an excessive quantity of automated, malicious requests. This could be a DDoS attack, an attempt to brute-force passwords to website administrative panels, or an attempt to exploit known vulnerabilities in software to attack the website and use it to send spam or modify the site contents.

      Such automated attacks may come from many different distributed servers in many different countries, making it difficult to block. One solution to mitigate the effects of an attack like this is to create an allowlist of countries that can access the website.

      It’s not a perfect solution, but in situations where restricting access to the website based on the visitor’s geographical location is a sensible choice and does not limit the website’s audience, this solution has the benefit of being fast and less error-prone.

      Filtering at the server level is faster than filtering at the website level and also covers all requests (including static files, like images). This kind of filtering also prevents requests from reaching the website software directly, making vulnerabilities harder to exploit.

      To make use of the geographical filtering, we must first install the Nginx GeoIP module as well as the GeoIP database containing the mappings between visitors’ IP addresses and their respective countries. To do so, let’s execute:

      • sudo apt install libnginx-mod-http-geoip geoip-database

      Now, let’s first create a new configuration file:

      • sudo nano /etc/nginx/conf.d/geoip.conf

      Paste the following contents into the file. This tells Nginx where to find the GeoIP database to identify countries based on the visitors’ IP addresses:

      /etc/nginx/conf.d/geoip.conf

      # GeoIP database path
      #
      
      geoip_country /usr/share/GeoIP/GeoIP.dat;
      

      The next step is to create the necessary map and restriction configuration. Open the default server block Nginx configuration:

      • sudo nano /etc/nginx/sites-available/default

      Find the server configuration block which, after the modifications in steps 1 and 2, looks like this:

      /etc/nginx/sites-available/default

      . . .
      # Default server configuration
      #
      
      # Old website redirect map
      #
      map $uri $new_uri {
          /old.html /index.html;
      }
      
      server {
          listen 80 default_server;
          listen [::]:80 default_server;
      
          # Old website redirect
          if ($new_uri) {
              rewrite ^ $new_uri permanent;
          }
      . . .
      

      We’ll be adding two new sections: one before the server block and one inside it.

      The section before the server block is a new map block, which defines the default action (access disallowed) as well as the list of country codes allowed to access the website. The section inside the server block denies access to the website if the map result says so:

      Modified /etc/nginx/sites-available/default

      . . .
      # Default server configuration
      #
      
      # Allowed countries
      #
      map $geoip_country_code $allowed_country {
          default no;
          country_code_1 yes;
          country_code_2 yes;
      }
      
      # Old website redirect map
      #
      map $uri $new_uri {
          /old.html /index.html;
      }
      
      server {
          listen 80 default_server;
          listen [::]:80 default_server;
      
          # Disallow access based on GeoIP
          if ($allowed_country = no) {
              return 444;
          }
      
          # Old website redirect
          if ($new_uri) {
              rewrite ^ $new_uri permanent;
          }
      . . .
      

      Save and close the file to exit.

      Here, we used country_code_1 and country_code_2 as placeholders. Replace these variables with the two-character country code for the country or countries you want to allow. You can use the ISO’s full, searchable list of all country codes to find. For example, the code for the United States is US.

      Unlike the first example, in this map block, the $allowed_country variable will always be set to something. By default, it’s set to no; if the $geoip_country_code variable matches one of the country codes in the block, it’s set to yes. If the $allowed_country variable is no, we return a 444 Connection Closed Without Response instead of serving the actual website.

      To enable the new configuration, restart Nginx:

      • sudo systemctl restart nginx

      If you didn’t add your country to the allowlist, when you try to visit http://your_server_ip, you’ll see an error message like The page isn’t working or The page didn’t send any data. If you did add your country to the allowlist, you’ll see Home as before.

      Conclusion

      The map module not only allows simple comparisons but also supports regular expressions allowing more complex matches. It is a great way to make configuration files clearer if multiple conditions must be evaluated.

      More detailed information can be found in Nginx’s official map module documentation.



      Source link

      How to Use Nginx’s map Module on Ubuntu 20.04


      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Nginx’s map module lets you create variables in Nginx’s configuration file whose values are conditional — that is, they depend on other variables’ values. In this guide, we will look at how to use Nginx’s map module to implement two examples: setting up a list of redirects from old website URLs to new ones and creating a whitelist of countries to control traffic to your website.

      Prerequisites

      To follow this tutorial, you will need:

      Step 1 — Creating and Testing an Example Webpage

      First, we will create a test file representing a newly published website. We’ll use this file to test our configuration.

      Let’s create a simple page, index.html, in the default Nginx website directory. This file will just have plain text describing what’s inside: Home:

      • sudo sh -c 'echo "Home" > /var/www/html/index.html'

      With this test file in place, we’ll check that it’s being served correctly with curl. We don’t need to specify index.html for this command because that file is served by default if no exact filename is provided:

      In response, you should see a single word saying Home just like below:

      Output

      Home

      Now let’s try to access a file that doesn’t exist in /var/www/html/, like old.html:

      • curl -L http://localhost/old.html

      The response will be a system error message, 404 Not Found, meaning the page does not exist:

      Output

      <html> <head><title>404 Not Found</title></head> <body bgcolor="white"> <center><h1>404 Not Found</h1></center> <hr><center>nginx/1.18.0 (Ubuntu)</center> </body> </html>

      We’re just using a dummy website in this tutorial, but if old.html was a page on a real website that used to exist and was deleted, returning a 404 would mean that all links to that page are broken. This is less than ideal because those links may have been indexed by Google, printed out or written down, or shared by any other means.

      In the next step, we’ll utilize the map module to make sure this old address will work again by redirecting viewers to the new replacements automatically.

      Step 2 — Configuring the Redirects

      For small websites with only a few pages, simple if conditional statements can be used for redirects and similar things. However, such a configuration is not easy to maintain or extend in the long run as the list of conditions grows longer.

      The map module is a more elegant, concise solution. It allows you to compare Nginx variable values against a list of conditions and then associate a new value with the variable depending on the match. In this example, we’ll be comparing the requested URL with the list of old pages that we want to redirect to their new counterparts. For each old address, we’ll associate the new one.

      The map module is a core Nginx module, which means it doesn’t need to be installed separately. To create the necessary map and redirect configuration, open the default server block Nginx configuration file in nano or your favorite text editor:

      • sudo nano /etc/nginx/sites-available/default

      Find the server configuration block, which looks like this:

      /etc/nginx/sites-available/default

      . . .
      # Default server configuration
      #
      
      server {
          listen 80 default_server;
          listen [::]:80 default_server;
      
      . . .
      

      We’ll be adding two new sections: one before the server block, and one inside it.

      The section before the server block is a new map block, which defines the mapping between the old URLs and the new ones using the map module. The section inside the server block is the redirect itself:

      /etc/nginx/sites-available/default

      . . .
      # Default server configuration
      #
      
      # Old website redirect map
      #
      map $uri $new_uri {
          /old.html /index.html;
      }
      
      server {
          listen 80 default_server;
          listen [::]:80 default_server;
      
          # Old website redirect
          if ($new_uri) {
              rewrite ^ $new_uri permanent;
          }
      . . .
      

      The map $uri $new_uri directive takes the contents of system $uri variable, which contains the URL address of the requested page, and then compares it against the list of conditions in the curly brackets. Each item in the list of conditions has two sections: the value to match against and the new value to assign to the variable if it matches.

      The line /old.html /index.html inside the map block means that if $uri’s value is /old.html, $new_uri will be changed to /index.html. If it doesn’t match, it’s not changed. Here, we only define one condition, but you can define as many conditions as you want in a map.

      Using a conditional if statement inside the server block, we check if the $new_uri variable’s value is set. If it is, it means the condition in the map was satisfied, and we should redirect to the new website using the rewrite command. The permanent keyword ensured that the redirect will be a 301 Moved Permanently HTTP redirect, which means that the old address is no longer valid and will not come back online.

      Save and close the file to exit.

      To enable the new configuration, restart Nginx:

      • sudo systemctl restart nginx

      To test the new configuration, execute the same request as before:

      • curl -L http://localhost/old.html

      This time there will be no 404 Not Found error in the output. Instead, you’ll see the simple home page we created in Step 1:

      Output

      Home

      This means the map has been appropriately configured, and you can use it to redirect URLs by adding more entries to the map.

      Redirecting URLs is one useful application of the map module. Another, which we’ll explore in the next step, is filtering traffic based on the visitors’ geographical location.

      Step 3 — Restricting Website Access to Certain Countries

      Sometimes, a server might receive an excessive quantity of automated, malicious requests. This could be a DDoS attack, an attempt to brute-force passwords to website administrative panels, or an attempt to exploit known vulnerabilities in software to attack the website and use it to send spam or modify the site contents.

      Such automated attacks may come from many different distributed servers in many different countries, making it difficult to block. One solution to mitigate the effects of an attack like this is to create a whitelist of countries that can access the website.

      It’s not a perfect solution, but in situations where restricting access to the website based on the visitor’s geographical location is a sensible choice and does not limit the website’s audience, this solution has the benefit of being fast and less error-prone.

      Filtering at the server level is faster than filtering at the website level and also covers all requests (including static files, like images). This kind of filtering also prevents requests from reaching the website software directly, making vulnerabilities harder to exploit.

      To make use of the geographical filtering, we must first install the Nginx GeoIP module as well as the GeoIP database containing the mappings between visitors’ IP addresses and their respective countries. To do so, let’s execute:

      • sudo apt install libnginx-mod-http-geoip geoip-database

      Now, let’s first create a new configuration file:

      • sudo nano /etc/nginx/conf.d/geoip.conf

      Paste the following contents into the file. This tells Nginx where to find the GeoIP database to identify countries based on the visitors’ IP addresses:

      /etc/nginx/conf.d/geoip.conf

      # GeoIP database path
      #
      
      geoip_country /usr/share/GeoIP/GeoIP.dat;
      

      The next step is to create the necessary map and restriction configuration. Open the default server block Nginx configuration:

      • sudo nano /etc/nginx/sites-available/default

      Find the server configuration block which, after the modifications in steps 1 and 2, looks like this:

      /etc/nginx/sites-available/default

      . . .
      # Default server configuration
      #
      
      # Old website redirect map
      #
      map $uri $new_uri {
          /old.html /index.html;
      }
      
      server {
          listen 80 default_server;
          listen [::]:80 default_server;
      
          # Old website redirect
          if ($new_uri) {
              rewrite ^ $new_uri permanent;
          }
      . . .
      

      We’ll be adding two new sections: one before the server block and one inside it.

      The section before the server block is a new map block, which defines the default action (access disallowed) as well as the list of country codes allowed to access the website. The section inside the server block denies access to the website if the map result says so:

      Modified /etc/nginx/sites-available/default

      . . .
      # Default server configuration
      #
      
      # Allowed countries
      #
      map $geoip_country_code $allowed_country {
          default no;
          country_code_1 yes;
          country_code_2 yes;
      }
      
      # Old website redirect map
      #
      map $uri $new_uri {
          /old.html /index.html;
      }
      
      server {
          listen 80 default_server;
          listen [::]:80 default_server;
      
          # Disallow access based on GeoIP
          if ($allowed_country = no) {
              return 444;
          }
      
          # Old website redirect
          if ($new_uri) {
              rewrite ^ $new_uri permanent;
          }
      . . .
      

      Save and close the file to exit.

      Here, we used country_code_1 and country_code_2 as placeholders. Replace these variables with the two-character country code for the country or countries you want to whitelist. You can use the ISO’s full, searchable list of all country codes to find. For example, the code for the United States is US.

      Unlike the first example, in this map block, the $allowed_country variable will always be set to something. By default, it’s set to no; if the $geoip_country_code variable matches one of the country codes in the block, it’s set to yes. If the $allowed_country variable is no, we return a 444 Connection Closed Without Response instead of serving the actual website.

      To enable the new configuration, restart Nginx:

      • sudo systemctl restart nginx

      If you didn’t add your country to the whitelist, when you try to visit http://your_server_ip, you’ll see an error message like The page isn’t working or The page didn’t send any data. If you do add your country to the whitelist, you’ll see Home as before.

      Conclusion

      The map module not only allows simple comparisons but also supports regular expressions allowing more complex matches. It is a great way to make configuration files clearer if multiple conditions must be evaluated.

      More detailed information can be found in Nginx’s official map module documentation.



      Source link

      How To Build a Custom Terraform Module


      Introduction

      Terraform modules encapsulate distinct logical components of your infrastructure by grouping their resources together. You can reuse them later with possible customizations, without repeating the resource definitions each time you need them, which is beneficial to large and complexly structured projects. You can customize module instances using input variables you define as well as extract information from them using outputs. Aside from creating your own custom modules, you can also use the pre-made modules published publicly at the Terraform Registry. Developers can use and customize them using inputs like the modules you create, but their source code is stored in and pulled from the cloud.

      In this tutorial, you’ll create a Terraform module that will set up multiple Droplets behind a Load Balancer for redundancy. You’ll also use the for_each and count looping features of the Hashicorp Configuration Language (HCL) to deploy multiple customized instances of the module at the same time.

      Prerequisites

      Note: This tutorial has specifically been tested with Terraform 0.13.

      Module Structure and Benefits

      In this section, you’ll learn what benefits modules bring, where they are usually placed in the project, and how they should be structured.

      Custom Terraform modules are created to encapsulate connected components that are used and deployed together frequently in bigger projects. They are self contained, bundling only the resources, variables, and providers they need.

      Modules are typically stored in a central folder in the root of the project, each in its respective subfolder underneath. In order to retain a clean separation between modules, always architect them to have a single purpose and make sure they never contain submodules.

      It is useful to create modules from your resource schemes when you find yourself repeating them with infrequent customizations. Packaging a single resource as a module can be superfluous and gradually removes the simplicity of the overall architecture.

      For small development and test projects, incorporating modules is not necessary because they do not bring much improvement in those cases. Modules, with their ability for customization, are the building element of complexly structured projects. Developers use modules for larger projects because of the significant advantages in avoiding code duplication. Modules also offer the benefit that definitions only need modification in one place, which will then be propagated through the rest of the infrastructure.

      Next you’ll define, use, and customize modules in your Terraform projects.

      Creating a Module

      In this section, you’ll define multiple Droplets and a Load Balancer as Terraform resources and package them into a module. You’ll also make the resulting module customizable using module inputs.

      You’ll store the module in a directory named droplet-lb, under a directory called modules. Assuming you are in the terraform-modules directory you created as part of the prerequisites, create both at once by running:

      • mkdir -p modules/droplet-lb

      The -p argument instructs mkdir to create all directories in the supplied path.

      Navigate to it:

      As was noted in the previous section, modules contain the resources and variables they use. Starting from Terraform 0.13, they must also include definitions of the providers they use. Modules do not require any special configuration to note that the code represents a module, as Terraform regards every directory containing HCL code as a module, even the root directory of the project.

      Variables defined in a module are exposed as its inputs and can be used in resource definitions to customize them. The module you’ll create will have two inputs: the number of Droplets to create and the name of their group. Create and open for editing a file called variables.tf where you’ll store the variables:

      Add the following lines:

      variables.tf

      variable "droplet_count" {}
      variable "group_name" {}
      

      Save and close the file.

      You’ll store the Droplet definition in a file named droplets.tf. Create and open it for editing:

      Add the following lines:

      droplets.tf

      resource "digitalocean_droplet" "droplets" {
        count  = var.droplet_count
        image  = "ubuntu-18-04-x64"
        name   = "${var.group_name}-${count.index}"
        region = "fra1"
        size   = "s-1vcpu-1gb"
      }
      

      For the count parameter, which specifies how many instances of a resource to create, you pass in the droplet_count variable. Its value will be specified when the module is called from the main project code. The name of each of the deployed Droplets will be different, which you achieve by appending the index of the current Droplet to the supplied group name. Deployment of the Droplets will be in the fra1 region and they will run Ubuntu 18.04.

      When you are done, save and close the file.

      With the Droplets now defined, you can move on to creating the Load Balancer. You’ll store its resource definition in a file named lb.tf. Create and open it for editing by running:

      Add its resource definition:

      lb.tf

      resource "digitalocean_loadbalancer" "www-lb" {
        name   = "lb-${var.group_name}"
        region = "fra1"
      
        forwarding_rule {
          entry_port     = 80
          entry_protocol = "http"
      
          target_port     = 80
          target_protocol = "http"
        }
      
        healthcheck {
          port     = 22
          protocol = "tcp"
        }
      
        droplet_ids = [
          for droplet in digitalocean_droplet.droplets:
            droplet.id
        ]
      }
      

      You define the Load Balancer with the group name in its name in order to make it distinguishable. You deploy it in the fra1 region together with the Droplets. The next two sections specify the target and monitoring ports and protocols.

      The highlighted droplet_ids block takes in the IDs of the Droplets, which should be managed by the Load Balancer. Since there are multiple Droplets, and their count is not known in advance, you use a for loop to traverse the collection of Droplets (digitalocean_droplet.droplets) and take their IDs. You surround the for loop with brackets ([]) so that the resulting collection will be a list.

      You’ve now defined the Droplet, Load Balancer, and variables for your module. You’ll need to define the provider requirements, specifying which providers the module uses, including their version and where they are located. Since Terraform 0.13, modules must explicitly define the sources of non-Hashicorp maintained providers they use; this is because they do not inherit them from the parent project.

      You’ll store the provider requirements in a file named provider.tf. Create it for editing by running:

      Add the following lines to require the digitalocean provider:

      provider.tf

      terraform {
        required_providers {
          digitalocean = {
            source = "digitalocean/digitalocean"
          }
        }
        required_version = ">= 0.13"
      }
      

      Save and close the file when you’re done. The droplet-lb module now requires the digitalocean provider.

      Modules also support outputs, which you can use to extract internal information about the state of their resources. You’ll define an output that exposes the IP address of the Load Balancer, and store it in a file named outputs.tf. Create it for editing:

      Add the following definition:

      outputs.tf

      output "lb_ip" {
        value = digitalocean_loadbalancer.www-lb.ip
      }
      

      This output retrieves the IP address of the Load Balancer. Save and close the file.

      The droplet-lb module is now functionally complete and ready for deployment. You’ll call it from the main code, which you’ll store in the root of the project. First, navigate to it by going upward through your file directory two times:

      Then, create and open for editing a file called main.tf, in which you’ll use the module:

      Add the following lines:

      main.tf

      module "groups" {
        source = "./modules/droplet-lb"
      
        droplet_count = 3
        group_name    = "group1"
      }
      
      output "loadbalancer-ip" {
        value = module.groups.lb_ip
      }
      

      In this declaration you invoke the droplet-lb module located in the directory specified as source. You configure the input it provides, droplet_count and group_name, which is set to group1 so you’ll later be able to discern between instances.

      Since the Load Balancer IP output is defined in a module, it won’t automatically be shown when you apply the project. The solution to this is to create another output retrieving its value (loadbalancer_ip). Save and close the file when you’re done.

      Initialize the module by running:

      The output will look like this:

      Output

      Initializing modules... - droplet-lb in modules/droplet-lb Initializing the backend... Initializing provider plugins... - Using previously-installed digitalocean/digitalocean v1.22.2 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.

      You can try planning the project to see what actions Terraform would take by running:

      • terraform plan -var "do_token=${DO_PAT}"

      The output will be similar to this:

      Output

      ... 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: # module.groups.digitalocean_droplet.droplets[0] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "group1-0" ... } # module.groups.digitalocean_droplet.droplets[1] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "group1-1" ... } # module.groups.digitalocean_droplet.droplets[2] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "group1-2" ... } # module.groups.digitalocean_loadbalancer.www-lb will be created + resource "digitalocean_loadbalancer" "www-lb" { ... + name = "group1-lb" ... } Plan: 4 to add, 0 to change, 0 to destroy. ...

      This output details that Terraform would create three Droplets, named group1-0, group1-1, and group1-2, and would also create a Load Balancer called group1-lb, which will manage the traffic to and from the three Droplets.

      You can try applying the project to the cloud by running:

      • terraform apply -var "do_token=${DO_PAT}"

      Enter yes when prompted. The output will show all the actions and the IP address of the Load Balancer will also be shown:

      Output

      module.groups.digitalocean_droplet.droplets[1]: Creating... module.groups.digitalocean_droplet.droplets[0]: Creating... module.groups.digitalocean_droplet.droplets[2]: Creating... ... Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: loadbalancer-ip = ip_address

      Because you’ll modify the configuration significantly in the next step, destroy the deployed resources by running:

      • terraform destroy -var "do_token=${DO_PAT}"

      Enter yes when prompted. The output will end in:

      Output

      .. Destroy complete! Resources: 4 destroyed.

      In this step, you’ve created a module containing a customizable number of Droplets and a Load Balancer that will automatically be configured to manage their ingoing and outgoing traffic. You’ll now deploy multiple instances of a module from the same code using for_each and count.

      Deploying Multiple Module Instances

      In this section, you’ll use count and for_each to deploy the droplet-lb module multiple times, with customizations.

      Using count

      One way to deploy multiple instances of the same module at once is to pass in how many to the count parameter, which is automatically available to every module. Open main.tf for editing:

      Modify it to look like this:

      main.tf

      module "groups" {
        source = "./modules/droplet-lb"
      
        count  = 3
      
        droplet_count = 3
        group_name    = "group1-${count.index}"
      }
      

      By setting count to 3, you instruct Terraform to deploy the module three times, each with a different group name. When you’re done, save and close the file.

      Plan the deployment by running:

      • terraform plan -var "do_token=${DO_PAT}"

      The output will be long, and will look like this:

      Output

      ... 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: # module.groups[0].digitalocean_droplet.droplets[0] will be created ... # module.groups[0].digitalocean_droplet.droplets[1] will be created ... # module.groups[0].digitalocean_droplet.droplets[2] will be created ... # module.groups[0].digitalocean_loadbalancer.www-lb will be created ... # module.groups[1].digitalocean_droplet.droplets[0] will be created ... # module.groups[1].digitalocean_droplet.droplets[1] will be created ... # module.groups[1].digitalocean_droplet.droplets[2] will be created ... # module.groups[1].digitalocean_loadbalancer.www-lb will be created ... # module.groups[2].digitalocean_droplet.droplets[0] will be created ... # module.groups[2].digitalocean_droplet.droplets[1] will be created ... # module.groups[2].digitalocean_droplet.droplets[2] will be created ... # module.groups[2].digitalocean_loadbalancer.www-lb will be created ... Plan: 12 to add, 0 to change, 0 to destroy. ...

      Terraform details in the output that each of the three module instances would have three Droplets and a Load Balancer associated with them.

      Using for_each

      You can use for_each for modules when you require more complex instance customization, or when the number of instances depends on third-party data (often presented as maps) and is not known while writing the code.

      You’ll now define a map that pairs group names to Droplet counts and deploy instances of droplet-lb according to it. Open main.tf for editing by running:

      Modify the file to make it look like this:

      main.tf

      variable "group_counts" {
        type    = map
        default = {
          "group1" = 1
          "group2" = 3
        }
      }
      
      module "groups" {
        source   = "./modules/droplet-lb"
        for_each = var.group_counts
      
        droplet_count = each.value
        group_name    = each.key
      }
      

      You first define a map called group_counts that contains how many Droplets a given group should have. Then, you invoke the module droplet-lb, but specify that the for_each loop should operate on var.group_counts, the map you’ve defined just before. droplet_count takes each.value, the value of the current pair, which is the count of Droplets for the current group. group_name receives the name of the group.

      Save and close the file when you’re done.

      Try applying the configuration by running:

      • terraform plan -var "do_token=${DO_PAT}"

      The output will detail the actions Terraform would take to create the two groups with their Droplets and Load Balancers:

      Output

      ... 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: # module.groups["group1"].digitalocean_droplet.droplets[0] will be created ... # module.groups["group1"].digitalocean_loadbalancer.www-lb will be created ... # module.groups["group2"].digitalocean_droplet.droplets[0] will be created ... # module.groups["group2"].digitalocean_droplet.droplets[1] will be created ... # module.groups["group2"].digitalocean_droplet.droplets[2] will be created ... # module.groups["group2"].digitalocean_loadbalancer.www-lb will be created ...

      In this step, you’ve used count and for_each to deploy multiple customized instances of the same module, from the same code.

      Conclusion

      In this tutorial you’ve created and deployed Terraform modules. You’ve used modules to group logically linked resources together and customized them in order to deploy multiple different instances from a central code definition. You’ve also used outputs to show attributes of resources contained in the module.

      If you would like to learn more about Terraform, check out our How To Manage Infrastructure with Terraform series.



      Source link