I'm learning about creating a k8s cluster on DO using terraform, I've been trying to take the ID of the single K8s node I've created, and reference it from the loadbalancer.
The main reasoning for this is so that I can declare the FQDN in the .tf file.
First, here is the cluster declaration:
variable "digitalocean_token" {}
provider "digitalocean" {
token = "${var.digitalocean_token}"
}
resource "digitalocean_kubernetes_cluster" "foo" {
name = "foo"
region = "nyc1"
version = "1.12.1-do.2"
node_pool {
name = "woker-pool"
size = "s-1vcpu-2gb"
node_count = 1
}
}
And here is the load balancer declaration:
resource "digitalocean_loadbalancer" "foo" {
name = "k8s-lb.nyc1"
region = "nyc1"
forwarding_rule {
entry_port = 80
entry_protocol = "http"
target_port = 80
target_protocol = "http"
}
droplet_ids = ["${digitalocean_kubernetes_cluster.foo.node_pool.0.id}"]
}
output "loadbalancer_ip" {
value = "${digitalocean_loadbalancer.foo.ip}"
}
resource "digitalocean_record" "terraform" {
domain = "example.com" # "${digitalocean_domain.example.name}"
type = "A"
name = "terraform"
value = "${digitalocean_loadbalancer.foo.ip}"
}
# Output the FQDN for the record
output "fqdn" {
value = "${digitalocean_record.terraform.fqdn}"
}
I'm guessing that maybe the digitalocean_loadbalancer resources is only setup to work with individual droplets?
Here are the output errors: when I run terraform apply:
* output.loadbalancer_ip: Resource 'digitalocean_loadbalancer.foo' not found for variable 'digitalocean_loadbalancer.foo.ip'
* digitalocean_record.terraform: Resource 'digitalocean_loadbalancer.foo' not found for variable 'digitalocean_loadbalancer.foo.ip'
* digitalocean_loadbalancer.foo: droplet_ids.0: cannot parse '' as int: strconv.ParseInt: parsing "d4292e64-9c0a-4afb-83fc-83f239bcb4af": invalid syntax
Pt. 2
I added a digitalocean_droplet resource, to see what kind of id was passed to the load balancer.
resource "digitalocean_droplet" "web" {
name = "web-1"
size = "s-1vcpu-1gb"
image = "ubuntu-18-04-x64"
region = "nyc1"
}
digitalocean_kubernetes_cluster.foo.node_pool.0.id = '6ae6a787-d837-4e78-a915-cb52155f66fe'
digitalocean_droplet.web.id = 132533158
So, the digitalocean_loadbalancer resource has an optional droplet_tag argument, which can be used to supply a common tag given to the created nodes/droplets.
However, when declaring a load-balancer inside kubernetes, a new one will still be created. So for now at least, it would appear that defining the domain/CNAME record with terraform isn't possible on digitalocean
You're using the wrong attribute reference for your load balancer droplet ids.
droplet_ids = ["${digitalocean_kubernetes_cluster.foo.node_pool.0.id}"]
This will use the node_pool id linked here
What you actually need to do is use the node_pool nodes id, which is referenced here
droplet_ids = "${digitalocean_kubernetes_cluster.foo.node_pool.0.nodes}"
The next problem you're going to have is that this returns a list of maps, and you'll need to build a list of ids from that. I'm not currently sure how to solve that, I'm afraid, but this should move you along hopefully.
It seems from your answer however, that what you want to do is update DNS for your loadbalancer.
You can do this external-dns using the digitalocean provider
Simply deploy this as a pod, specifying the required configuration, and ensure that the arg --source=service is set.
If you want to go a step further, and allow updating DNS with specific hostname, deploy an ingress controller like nginx-ingress and specify ingresses for your applications. The external-dns deployment (if you set --source=ingress) will the hostname from your ingress and update DNS for you.
Related
I am trying to deploy an helm chart to a k8s cluster using terraform, it runs fine the first time, but when I try to update the chart I found myself with this error:
Error: cannot re-use a name that is still in use │ │ with helm_release.deliverable, │ on main.tf line 8, in resource "helm_release" "deliverable": │ 8: resource "helm_release" "deliverable" {
Now, if I delete the chart it will work fine ( the first run ), but I don't want to do that everytime, is there a way to reutilise the releasename?
here is my code:
resource "helm_release" "deliverable" {
name = "releaseapp"
namespace = "appnamespace"
repository = "https://myrepo/repository/helm/"
chart = "releaseapp"
version = "0.2.0"
repository_username = "myuser"
force_update = true
wait = true
repository_password = var.acrpass
set {
name = "namespace"
value = "appnamespace"
}
set {
name = "image.tag"
value = var.applicationversion
}
set {
name = "image.tag"
value = var.applicationversion
}
set {
name = "timestamp"
value = local.timestamp_sanitized
}
}
locals {
timestamp = "${timestamp()}"
timestamp_sanitized = "${replace("${local.timestamp}", "/[- TZ:]/", "")}"
}
I tried to change the helm chart version
I tried to add both
force_update = true
wait = true
but still no success. Would you be able to help me, please?
Gitlab CI infrastructure terraform state wont accept special characters in its name, so no state were created. Therefore when trying to read from it, it failed and tries to create a new resource that already existed.
I had my state name create from the git branch that in the first case was feature/jira-task
changing from:
${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}_${CI_COMMIT_BRANCH}
TO:
${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}_tfstate
Solved the problem.
I'm trying to create a unique terraform plan that can automate the installation of 2 separate environments in 2 Azure tenants.
Environment A consists of:
A container registry
A kubernetes cluster
Environment B consists of:
A kubernetes cluster that uses the registry in Environment A.
They will not share the same tfstate.
We don't want to have to maintain 2 terraform plans.
Idea: (We cannot test the solution at the moment).
we create a variable.tf where we define a variable that will contain the registry Id.
variable.tf:
variable DeployRegistry {
default = "True" or "False" (True for envA, False for envB)
}
variable registry_acr_id {
default = "/subscriptions/xxx/resourceGroups/yyy/providers/Microsoft.ContainerRegistry/registries/zzz"
}
registry.tf creates the registry and set output:
resource "azurerm_container_registry" "acr" {
count = "${var.DeployRegistry == "true" ? 1 : 0}"
etc...
}
output "registry_acr_id" {
value = "azurerm_container_registry.acr.id
sensitive = false
}
With condition DeployRegistry set to false, we can disable the creation of the registry and use registry_acr_id passed as input (eventually in tfvar).
Question: Will it work or will output "registry_acr_id" be empty when we are not creating it?
Because there is a condition to create or not the resource, but output is inconditionnally set.
You probably want something like this:
variable "registry" {
type = string
default = null
}
locals {
registry = var.registry != null ? var.registry : azurerm_container_registry.acr[0].id
}
resource "azurerm_container_registry" "acr" {
count = var.registry == null ? 1 : 0
# ...
}
output "registry" {
value = local.registry
}
The above code creates and outputs the new registry if the variable "registry" is not set. If the registry is set to something, it assumes there is no need to create new one.
I'm trying to deploy a crd using terraform with the code bellow
resource "kubectl_manifest" "crd-deploy" {
for_each = [ for crd in var.crdslist : crd ]
yaml_body = (fileexists("${path.module}/crds/${crd}.yaml") ? file("${path.module}/crds/${crd}") : "")
}
but i still get an error
Error: Invalid reference
on ../../00-modules/00-global/25-crd/10-crd.tf line 3, in resource "kubectl_manifest" "crd-deploy":
3: yaml_body = (fileexists("${path.module}/crds/${crd}.yaml") ? file("${path.module}/crds/${crd}") : "")
A reference to a resource type must be followed by at least one attribute access, specifying the resource name.
input.tf in module_level
variable "crdslist" {
type = list(string)
default = []
}
input.tf in execution_module_level
locals {
crdslist = ["crd-test"]
}
From where i run the terraform plan to deploy K8s CRDs
module "crds" {
source = "../../modules/global/25-crd"
crdslist = local.crdslist
}
Since the variable that is used is of type list(string), that means it cannot be used with for_each in its original form. The for_each meta-argument requires a variable to be of type map or set [1]:
If a resource or module block includes a for_each argument whose value is a map or a set of strings, Terraform will create one instance for each member of that map or set.
In order for the current configuration to work as expected, it is enough to use a Terraform built-in function toset [2]. The only change that needs to be made is inside of the module:
resource "kubectl_manifest" "crd-deploy" {
for_each = toset(var.crdslist)
yaml_body = (fileexists("${path.module}/crds/${each.vlaue}.yaml") ? file("${path.module}/crds/${each.vlaue}") : "")
}
Note that when the list is converted to a set, the key and value are the same, i.e., each.key and each.value can be used interchangeably [3].
[1] https://www.terraform.io/language/meta-arguments/for_each
[2] https://www.terraform.io/language/functions/toset
[3] https://www.terraform.io/language/meta-arguments/for_each#the-each-object
We are developing a module to create Linux based NVAs. Each NVA needs to have 3 NICs attached to it, each on a different subnet. At the moment, the module is able to successfully create the 3 NICs but the output from the NIC creation is of type tuple and we have not figured out how to use the NIC Resource IDs stored in the tuple as input into the VM creation.
We are trying to keep the module generic enough to be able to use it to create other types of Linux VMs that may not have multiple NICs.
Working code that calls module to create the NICs:
module "linux_vm" {
source = "../modules/test_module"
resource_group_name = azurerm_resource_group.saca_rg.name
nic_names = ["nic-mgmt","nic-int","nic-ext"]
subnet_id = ["${local.subnet_ids[4]}","${local.subnet_ids[3]}","${local.subnet_ids[2]}"]
ip_allocation = ["Static","Static","Static"]
static_ip = ["${cidrhost(local.static_ips[4], 4)}","${cidrhost(local.static_ips[3], 4)}","${cidrhost(local.static_ips[2], 4)}"]
}
Module code that creates the NICs:
resource "azurerm_network_interface" "vm_nic" {
count = length(var.nic_names)
name = var.nic_names[count.index]
resource_group_name = data.azurerm_resource_group.rg.name
location = data.azurerm_resource_group.rg.location
ip_configuration {
name = "${var.nic_names[count.index]}-ipconfig"
subnet_id = var.subnet_id[count.index]
private_ip_address_allocation = var.ip_allocation[count.index]
private_ip_address = var.static_ip[count.index]
}
}
The output from this is a tuple that resembles the output below:
"outputs": {
"nic_ids": {
"value": [
"/subscriptions/<sub_id>/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic-mgmt",
"/subscriptions/<sub_id>/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic-int",
"/subscriptions/<sub_id>/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic-ext"
],
"type": [
"tuple",
[
"string",
"string",
"string"
]
]
}
},
When we try to use this output as input into the VM creation block using the parameter below:
network_interface_ids = [azurerm_network_interface.vm_nic.*.id]
We get the error generated below:
Error: Incorrect attribute value type
on ../modules/test_module/main.tf line 27, in resource "azurerm_linux_virtual_machine" "vm":
27: network_interface_ids = [azurerm_network_interface.vm_nic.*.id]
|----------------
| azurerm_network_interface.vm_nic is tuple with 3 elements
Inappropriate value for attribute "network_interface_ids": element 0: string
required.
Discovered what the problem was.
We were trying to use the line below:
network_interface_ids = [azurerm_network_interface.vm_nic.*.id]
But since a tuple is like a list...this was basically nesting a list inside of a list. When we switched to the below, all 3 NICs assigned as expected:
network_interface_ids = azurerm_network_interface.vm_nic.*.id
I was working on a composite resource when I came across the issue of being able to dynamically pass parameters to a DSC resource and wondered if there's another way to tackle this that I'm missing.
Basically, I have the an array (Below) that contains one PSObject per desired file-share. Each of these PSObjects has properties that are used as parameters for my DSC resource (In this case the cLocalFileShare community resource).
The issue is, not all of these objects have all of the parameters defined. For example, some of my shares don't have any users/groups assigned to the ReadAccess permission, but in my ForEach loop (Below), a $null value is being passed to the actual resource as this permissions isn't defined, and this causes the resource to error as it is trying to set ReadAccess permissions to user $null.
My issue is, how do I tackle this - for this resource and others?
I've tried splatting the parameters in the DSC resource, but this doesn't seem to be supported. If this worked, I could build a different parameter list and pass that.
Somebody on Reddit suggested passing a string that contained all of the parameters, but again this doesn't seem to be supported.
My worst fear is, I will have to edit each resource to support (and ultimately ignore) $null values which seems like a really bad way to tackle this.
So, here's my array containing a PSObject per file share.
$MyConfig = #(
#{
Path = 'D:\Shares\Accounting'
Name = 'Accounting'
Ensure = 'Present'
ChangeAccess = 'AccountingAdmins'
ReadAccess = 'AccountingInterns,FinanceDepartment'
}
#{
Path = 'D:\Shares\Software'
Name = 'Software$'
Ensure = 'Present'
ReadAccess = 'DomainUsers'
}
)
Now, within the actual DSC configuration...
configuration {
ForEach ($ShareProperties in $MyConfig) {
# Each resource is named after the Path specified, but with the colon replaced as that's not valid character for the resource name
cLocalFileShare $ShareProperties.Path.Replace(':','__') {
Path = $ShareProperties.Path
Name = $ShareProperties.Name
Ensure = $ShareProperties.Ensure
ChangeAccess = $ShareProperties.ChangeAccess
ReadAccess = $ShareProperties.ReadAccess
}
}
}
Not the most elegant solution. But you could use a conditional section within your ForEach loop.
configuration {
ForEach ($ShareProperties in $MyConfig) {
# Each resource is named after the Path specified, but with the colon replaced as that's not valid character for the resource name
if ($ShareProperties.ChangeAccess -eq $null) {
cLocalFileShare $ShareProperties.Path.Replace(':','__') {
Path = $ShareProperties.Path
Name = $ShareProperties.Name
Ensure = $ShareProperties.Ensure
ReadAccess = $ShareProperties.ReadAccess
}
}
else
{
cLocalFileShare $ShareProperties.Path.Replace(':','__') {
Path = $ShareProperties.Path
Name = $ShareProperties.Name
Ensure = $ShareProperties.Ensure
ChangeAccess = $ShareProperties.ChangeAccess
ReadAccess = $ShareProperties.ReadAccess
}
}
}
}