diff --git a/docs/data-sources/loadbalancer.md b/docs/data-sources/loadbalancer.md index 7b5d8bb5..047321ce 100644 --- a/docs/data-sources/loadbalancer.md +++ b/docs/data-sources/loadbalancer.md @@ -79,8 +79,36 @@ Read-Only: Read-Only: - `acl` (Set of String) Load Balancer is accessible only from an IP address in this range. +- `observability` (Attributes) We offer Load Balancer metrics observability via ARGUS or external solutions. (see [below for nested schema](#nestedatt--options--observability)) - `private_network_only` (Boolean) If true, Load Balancer is accessible only via a private network IP address. + +### Nested Schema for `options.observability` + +Read-Only: + +- `logs` (Attributes) Observability logs configuration. (see [below for nested schema](#nestedatt--options--observability--logs)) +- `metrics` (Attributes) Observability metrics configuration. (see [below for nested schema](#nestedatt--options--observability--metrics)) + + +### Nested Schema for `options.observability.logs` + +Read-Only: + +- `credentials_ref` (String) Credentials reference for logs. +- `push_url` (String) Credentials reference for logs. + + + +### Nested Schema for `options.observability.metrics` + +Read-Only: + +- `credentials_ref` (String) Credentials reference for metrics. +- `push_url` (String) Credentials reference for metrics. + + + ### Nested Schema for `target_pools` diff --git a/docs/guides/using_loadbalancer_with_observability.md b/docs/guides/using_loadbalancer_with_observability.md new file mode 100644 index 00000000..106d8797 --- /dev/null +++ b/docs/guides/using_loadbalancer_with_observability.md @@ -0,0 +1,167 @@ +--- +page_title: "Using the STACKIT Loadbalancer together with STACKIT Observability" +--- +# Using the STACKIT Loadbalancer together with STACKIT Observability + +## Overview + +This guide explains how to configure the STACKIT Loadbalancer product to send metrics and logs to a STACKIT Observability instance. + +1. **Set Up Providers** + + Begin by configuring the STACKIT provider to connect to the STACKIT services. + + ```hcl + provider "stackit" { + default_region = "eu01" + } + ``` + +2. **Create an Observability instance** + + Establish a STACKIT Observability instance and its credentials. + + ```hcl + resource "stackit_observability_instance" "observability01" { + project_id = var.project_id_prod + name = "example-instance" + plan_name = "Observability-Monitoring-Medium-EU01" + acl = ["0.0.0.0/0"] + metrics_retention_days = 30 + metrics_retention_days_5m_downsampling = 10 + metrics_retention_days_1h_downsampling = 5 + } + + resource "stackit_observability_credential" "observability01-credential" { + project_id = var.project_id_prod + instance_id = stackit_observability_instance.observability01.instance_id + } + ``` + +3. **Create STACKIT Loadbalancer credentials reference** + + Create a STACKIT Loadbalancer credentials which will be used in the STACKIT Loadbalancer resource as a reference. + + ```hcl + resource "stackit_loadbalancer_observability_credential" "example" { + project_id = var.project_id_prod + display_name = "example-credentials" + username = stackit_observability_credential.observability01-credential.username + password = stackit_observability_credential.observability01-credential.password + } + ``` + +4. **Create the STACKIT Loadbalancer** + + ```hcl + # Create a network + resource "stackit_network" "example_network" { + project_id = var.project_id_prod + name = "example-network" + ipv4_nameservers = ["8.8.8.8"] + ipv4_prefix = "192.168.0.0/25" + labels = { + "key" = "value" + } + routed = true + } + + # Create a network interface + resource "stackit_network_interface" "nic" { + project_id = var.project_id_prod + network_id = stackit_network.example_network.network_id + } + + # Create a public IP for the load balancer + resource "stackit_public_ip" "public-ip" { + project_id = var.project_id_prod + lifecycle { + ignore_changes = [network_interface_id] + } + } + + # Create a key pair for accessing the server instance + resource "stackit_key_pair" "keypair" { + name = "example-key-pair" + # set the path of your public key file here + public_key = chomp(file("/home/bob/.ssh/id_ed25519.pub")) + } + + # Create a server instance + resource "stackit_server" "boot-from-image" { + project_id = var.project_id_prod + name = "example-server" + boot_volume = { + size = 64 + source_type = "image" + source_id = "59838a89-51b1-4892-b57f-b3caf598ee2f" // Ubuntu 24.04 + } + availability_zone = "eu01-1" + machine_type = "g1.1" + keypair_name = stackit_key_pair.keypair.name + } + + # Attach the network interface to the server + resource "stackit_server_network_interface_attach" "nic-attachment" { + project_id = var.project_id_prod + server_id = stackit_server.boot-from-image.server_id + network_interface_id = stackit_network_interface.nic.network_interface_id + } + + # Create a load balancer + resource "stackit_loadbalancer" "example" { + project_id = var.project_id_prod + name = "example-load-balancer" + target_pools = [ + { + name = "example-target-pool" + target_port = 80 + targets = [ + { + display_name = stackit_server.boot-from-image.name + ip = stackit_network_interface.nic.ipv4 + } + ] + active_health_check = { + healthy_threshold = 10 + interval = "3s" + interval_jitter = "3s" + timeout = "3s" + unhealthy_threshold = 10 + } + } + ] + listeners = [ + { + display_name = "example-listener" + port = 80 + protocol = "PROTOCOL_TCP" + target_pool = "example-target-pool" + } + ] + networks = [ + { + network_id = stackit_network.example_network.network_id + role = "ROLE_LISTENERS_AND_TARGETS" + } + ] + external_address = stackit_public_ip.public-ip.ip + options = { + private_network_only = false + observability = { + logs = { + # uses the load balancer credential from the last step + credentials_ref = stackit_loadbalancer_observability_credential.example.credentials_ref + # uses the observability instance from step 1 + push_url = stackit_observability_instance.observability01.logs_push_url + } + metrics = { + # uses the load balancer credential from the last step + credentials_ref = stackit_loadbalancer_observability_credential.example.credentials_ref + # uses the observability instance from step 1 + push_url = stackit_observability_instance.observability01.metrics_push_url + } + } + } + } + ``` diff --git a/docs/resources/loadbalancer.md b/docs/resources/loadbalancer.md index 7a6c9125..009f543f 100644 --- a/docs/resources/loadbalancer.md +++ b/docs/resources/loadbalancer.md @@ -218,4 +218,30 @@ Optional: Optional: - `acl` (Set of String) Load Balancer is accessible only from an IP address in this range. +- `observability` (Attributes) We offer Load Balancer metrics observability via ARGUS or external solutions. Not changeable after creation. (see [below for nested schema](#nestedatt--options--observability)) - `private_network_only` (Boolean) If true, Load Balancer is accessible only via a private network IP address. + + +### Nested Schema for `options.observability` + +Optional: + +- `logs` (Attributes) Observability logs configuration. Not changeable after creation. (see [below for nested schema](#nestedatt--options--observability--logs)) +- `metrics` (Attributes) Observability metrics configuration. Not changeable after creation. (see [below for nested schema](#nestedatt--options--observability--metrics)) + + +### Nested Schema for `options.observability.logs` + +Optional: + +- `credentials_ref` (String) Credentials reference for logs. Not changeable after creation. +- `push_url` (String) Credentials reference for logs. Not changeable after creation. + + + +### Nested Schema for `options.observability.metrics` + +Optional: + +- `credentials_ref` (String) Credentials reference for metrics. Not changeable after creation. +- `push_url` (String) Credentials reference for metrics. Not changeable after creation. diff --git a/stackit/internal/services/loadbalancer/loadbalancer/datasource.go b/stackit/internal/services/loadbalancer/loadbalancer/datasource.go index 33514471..6e8521d8 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/datasource.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/datasource.go @@ -81,38 +81,45 @@ func (r *loadBalancerDataSource) Configure(ctx context.Context, req datasource.C // Schema defines the schema for the data source. func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { descriptions := map[string]string{ - "main": "Load Balancer data source schema. Must have a `region` specified in the provider configuration.", - "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"region\",\"`name`\".", - "project_id": "STACKIT project ID to which the Load Balancer is associated.", - "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", - "listeners": "List of all listeners which will accept traffic. Limited to 20.", - "port": "Port number where we listen for traffic.", - "protocol": "Protocol is the highest network protocol we understand to load balance.", - "target_pool": "Reference target pool by target pool name.", - "name": "Load balancer name.", - "networks": "List of networks that listeners and targets reside in.", - "network_id": "Openstack network ID.", - "role": "The role defines how the load balancer is using the network.", - "options": "Defines any optional functionality you want to have enabled on your load balancer.", - "acl": "Load Balancer is accessible only from an IP address in this range.", - "private_network_only": "If true, Load Balancer is accessible only via a private network IP address.", - "session_persistence": "Here you can setup various session persistence options, so far only \"`use_source_ip_address`\" is supported.", - "use_source_ip_address": "If true then all connections from one source IP address are redirected to the same target. This setting changes the load balancing algorithm to Maglev.", - "server_name_indicators": "A list of domain names to match in order to pass TLS traffic to the target pool in the current listener", - "server_name_indicators.name": "A domain name to match in order to pass TLS traffic to the target pool in the current listener", - "private_address": "Transient private Load Balancer IP address. It can change any time.", - "target_pools": "List of all target pools which will be used in the Load Balancer. Limited to 20.", - "healthy_threshold": "Healthy threshold of the health checking.", - "interval": "Interval duration of health checking in seconds.", - "interval_jitter": "Interval duration threshold of the health checking in seconds.", - "timeout": "Active health checking timeout duration in seconds.", - "unhealthy_threshold": "Unhealthy threshold of the health checking.", - "target_pools.name": "Target pool name.", - "target_port": "Identical port number where each target listens for traffic.", - "targets": "List of all targets which will be used in the pool. Limited to 1000.", - "targets.display_name": "Target display name", - "ip": "Target IP", - "region": "The resource region. If not defined, the provider region is used.", + "main": "Load Balancer data source schema. Must have a `region` specified in the provider configuration.", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"region\",\"`name`\".", + "project_id": "STACKIT project ID to which the Load Balancer is associated.", + "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", + "listeners": "List of all listeners which will accept traffic. Limited to 20.", + "port": "Port number where we listen for traffic.", + "protocol": "Protocol is the highest network protocol we understand to load balance.", + "target_pool": "Reference target pool by target pool name.", + "name": "Load balancer name.", + "networks": "List of networks that listeners and targets reside in.", + "network_id": "Openstack network ID.", + "role": "The role defines how the load balancer is using the network.", + "observability": "We offer Load Balancer metrics observability via ARGUS or external solutions.", + "observability_logs": "Observability logs configuration.", + "observability_logs_credentials_ref": "Credentials reference for logs.", + "observability_logs_push_url": "The ARGUS/Loki remote write Push URL to ship the logs to.", + "observability_metrics": "Observability metrics configuration.", + "observability_metrics_credentials_ref": "Credentials reference for metrics.", + "observability_metrics_push_url": "The ARGUS/Prometheus remote write Push URL to ship the metrics to.", + "options": "Defines any optional functionality you want to have enabled on your load balancer.", + "acl": "Load Balancer is accessible only from an IP address in this range.", + "private_network_only": "If true, Load Balancer is accessible only via a private network IP address.", + "session_persistence": "Here you can setup various session persistence options, so far only \"`use_source_ip_address`\" is supported.", + "use_source_ip_address": "If true then all connections from one source IP address are redirected to the same target. This setting changes the load balancing algorithm to Maglev.", + "server_name_indicators": "A list of domain names to match in order to pass TLS traffic to the target pool in the current listener", + "server_name_indicators.name": "A domain name to match in order to pass TLS traffic to the target pool in the current listener", + "private_address": "Transient private Load Balancer IP address. It can change any time.", + "target_pools": "List of all target pools which will be used in the Load Balancer. Limited to 20.", + "healthy_threshold": "Healthy threshold of the health checking.", + "interval": "Interval duration of health checking in seconds.", + "interval_jitter": "Interval duration threshold of the health checking in seconds.", + "timeout": "Active health checking timeout duration in seconds.", + "unhealthy_threshold": "Unhealthy threshold of the health checking.", + "target_pools.name": "Target pool name.", + "target_port": "Identical port number where each target listens for traffic.", + "targets": "List of all targets which will be used in the pool. Limited to 1000.", + "targets.display_name": "Target display name", + "ip": "Target IP", + "region": "The resource region. If not defined, the provider region is used.", } resp.Schema = schema.Schema{ @@ -222,6 +229,40 @@ func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRe Description: descriptions["private_network_only"], Computed: true, }, + "observability": schema.SingleNestedAttribute{ + Description: descriptions["observability"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "logs": schema.SingleNestedAttribute{ + Description: descriptions["observability_logs"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "credentials_ref": schema.StringAttribute{ + Description: descriptions["observability_logs_credentials_ref"], + Computed: true, + }, + "push_url": schema.StringAttribute{ + Description: descriptions["observability_logs_credentials_ref"], + Computed: true, + }, + }, + }, + "metrics": schema.SingleNestedAttribute{ + Description: descriptions["observability_metrics"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "credentials_ref": schema.StringAttribute{ + Description: descriptions["observability_metrics_credentials_ref"], + Computed: true, + }, + "push_url": schema.StringAttribute{ + Description: descriptions["observability_metrics_credentials_ref"], + Computed: true, + }, + }, + }, + }, + }, }, }, "private_address": schema.StringAttribute{ diff --git a/stackit/internal/services/loadbalancer/loadbalancer/resource.go b/stackit/internal/services/loadbalancer/loadbalancer/resource.go index 3496b709..9134e5d6 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/resource.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/resource.go @@ -100,14 +100,36 @@ var networkTypes = map[string]attr.Type{ // Struct corresponding to Model.Options type options struct { - ACL types.Set `tfsdk:"acl"` - PrivateNetworkOnly types.Bool `tfsdk:"private_network_only"` + ACL types.Set `tfsdk:"acl"` + PrivateNetworkOnly types.Bool `tfsdk:"private_network_only"` + Observability types.Object `tfsdk:"observability"` } // Types corresponding to options var optionsTypes = map[string]attr.Type{ "acl": types.SetType{ElemType: types.StringType}, "private_network_only": types.BoolType, + "observability": types.ObjectType{AttrTypes: observabilityTypes}, +} + +type observability struct { + Logs types.Object `tfsdk:"logs"` + Metrics types.Object `tfsdk:"metrics"` +} + +var observabilityTypes = map[string]attr.Type{ + "logs": types.ObjectType{AttrTypes: observabilityOptionTypes}, + "metrics": types.ObjectType{AttrTypes: observabilityOptionTypes}, +} + +type observabilityOption struct { + CredentialsRef types.String `tfsdk:"credentials_ref"` + PushUrl types.String `tfsdk:"push_url"` +} + +var observabilityOptionTypes = map[string]attr.Type{ + "credentials_ref": types.StringType, + "push_url": types.StringType, } // Struct corresponding to Model.TargetPools[i] @@ -257,38 +279,45 @@ func (r *loadBalancerResource) Schema(_ context.Context, _ resource.SchemaReques roleOptions := []string{"ROLE_UNSPECIFIED", "ROLE_LISTENERS_AND_TARGETS", "ROLE_LISTENERS", "ROLE_TARGETS"} descriptions := map[string]string{ - "main": "Load Balancer resource schema.", - "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"region\",\"`name`\".", - "project_id": "STACKIT project ID to which the Load Balancer is associated.", - "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", - "listeners": "List of all listeners which will accept traffic. Limited to 20.", - "port": "Port number where we listen for traffic.", - "protocol": "Protocol is the highest network protocol we understand to load balance. " + utils.SupportedValuesDocumentation(protocolOptions), - "target_pool": "Reference target pool by target pool name.", - "name": "Load balancer name.", - "networks": "List of networks that listeners and targets reside in.", - "network_id": "Openstack network ID.", - "role": "The role defines how the load balancer is using the network. " + utils.SupportedValuesDocumentation(roleOptions), - "options": "Defines any optional functionality you want to have enabled on your load balancer.", - "acl": "Load Balancer is accessible only from an IP address in this range.", - "private_network_only": "If true, Load Balancer is accessible only via a private network IP address.", - "session_persistence": "Here you can setup various session persistence options, so far only \"`use_source_ip_address`\" is supported.", - "use_source_ip_address": "If true then all connections from one source IP address are redirected to the same target. This setting changes the load balancing algorithm to Maglev.", - "server_name_indicators": "A list of domain names to match in order to pass TLS traffic to the target pool in the current listener", - "server_name_indicators.name": "A domain name to match in order to pass TLS traffic to the target pool in the current listener", - "private_address": "Transient private Load Balancer IP address. It can change any time.", - "target_pools": "List of all target pools which will be used in the Load Balancer. Limited to 20.", - "healthy_threshold": "Healthy threshold of the health checking.", - "interval": "Interval duration of health checking in seconds.", - "interval_jitter": "Interval duration threshold of the health checking in seconds.", - "timeout": "Active health checking timeout duration in seconds.", - "unhealthy_threshold": "Unhealthy threshold of the health checking.", - "target_pools.name": "Target pool name.", - "target_port": "Identical port number where each target listens for traffic.", - "targets": "List of all targets which will be used in the pool. Limited to 1000.", - "targets.display_name": "Target display name", - "ip": "Target IP", - "region": "The resource region. If not defined, the provider region is used.", + "main": "Load Balancer resource schema.", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"region\",\"`name`\".", + "project_id": "STACKIT project ID to which the Load Balancer is associated.", + "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", + "listeners": "List of all listeners which will accept traffic. Limited to 20.", + "port": "Port number where we listen for traffic.", + "protocol": "Protocol is the highest network protocol we understand to load balance. " + utils.SupportedValuesDocumentation(protocolOptions), + "target_pool": "Reference target pool by target pool name.", + "name": "Load balancer name.", + "networks": "List of networks that listeners and targets reside in.", + "network_id": "Openstack network ID.", + "role": "The role defines how the load balancer is using the network. " + utils.SupportedValuesDocumentation(roleOptions), + "observability": "We offer Load Balancer metrics observability via ARGUS or external solutions. Not changeable after creation.", + "observability_logs": "Observability logs configuration. Not changeable after creation.", + "observability_logs_credentials_ref": "Credentials reference for logs. Not changeable after creation.", + "observability_logs_push_url": "The ARGUS/Loki remote write Push URL to ship the logs to. Not changeable after creation.", + "observability_metrics": "Observability metrics configuration. Not changeable after creation.", + "observability_metrics_credentials_ref": "Credentials reference for metrics. Not changeable after creation.", + "observability_metrics_push_url": "The ARGUS/Prometheus remote write Push URL to ship the metrics to. Not changeable after creation.", + "options": "Defines any optional functionality you want to have enabled on your load balancer.", + "acl": "Load Balancer is accessible only from an IP address in this range.", + "private_network_only": "If true, Load Balancer is accessible only via a private network IP address.", + "session_persistence": "Here you can setup various session persistence options, so far only \"`use_source_ip_address`\" is supported.", + "use_source_ip_address": "If true then all connections from one source IP address are redirected to the same target. This setting changes the load balancing algorithm to Maglev.", + "server_name_indicators": "A list of domain names to match in order to pass TLS traffic to the target pool in the current listener", + "server_name_indicators.name": "A domain name to match in order to pass TLS traffic to the target pool in the current listener", + "private_address": "Transient private Load Balancer IP address. It can change any time.", + "target_pools": "List of all target pools which will be used in the Load Balancer. Limited to 20.", + "healthy_threshold": "Healthy threshold of the health checking.", + "interval": "Interval duration of health checking in seconds.", + "interval_jitter": "Interval duration threshold of the health checking in seconds.", + "timeout": "Active health checking timeout duration in seconds.", + "unhealthy_threshold": "Unhealthy threshold of the health checking.", + "target_pools.name": "Target pool name.", + "target_port": "Identical port number where each target listens for traffic.", + "targets": "List of all targets which will be used in the pool. Limited to 1000.", + "targets.display_name": "Target display name", + "ip": "Target IP", + "region": "The resource region. If not defined, the provider region is used.", } resp.Schema = schema.Schema{ @@ -466,6 +495,51 @@ The example below creates the supporting infrastructure using the STACKIT Terraf boolplanmodifier.UseStateForUnknown(), }, }, + "observability": schema.SingleNestedAttribute{ + Description: descriptions["observability"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Object{ + // API docs says observability options are not changeable after creation + objectplanmodifier.RequiresReplace(), + }, + Attributes: map[string]schema.Attribute{ + "logs": schema.SingleNestedAttribute{ + Description: descriptions["observability_logs"], + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "credentials_ref": schema.StringAttribute{ + Description: descriptions["observability_logs_credentials_ref"], + Optional: true, + Computed: true, + }, + "push_url": schema.StringAttribute{ + Description: descriptions["observability_logs_credentials_ref"], + Optional: true, + Computed: true, + }, + }, + }, + "metrics": schema.SingleNestedAttribute{ + Description: descriptions["observability_metrics"], + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "credentials_ref": schema.StringAttribute{ + Description: descriptions["observability_metrics_credentials_ref"], + Optional: true, + Computed: true, + }, + "push_url": schema.StringAttribute{ + Description: descriptions["observability_metrics_credentials_ref"], + Optional: true, + Computed: true, + }, + }, + }, + }, + }, }, }, "private_address": schema.StringAttribute{ @@ -903,6 +977,7 @@ func toOptionsPayload(ctx context.Context, model *Model) (*loadbalancer.LoadBala if model.Options.IsNull() || model.Options.IsUnknown() { return &loadbalancer.LoadBalancerOptions{ AccessControl: &loadbalancer.LoadbalancerOptionAccessControl{}, + Observability: &loadbalancer.LoadbalancerOptionObservability{}, }, nil } @@ -922,8 +997,40 @@ func toOptionsPayload(ctx context.Context, model *Model) (*loadbalancer.LoadBala accessControlPayload.AllowedSourceRanges = &aclModel } + observabilityPayload := &loadbalancer.LoadbalancerOptionObservability{} + if !(optionsModel.Observability.IsNull() || optionsModel.Observability.IsUnknown()) { + observabilityModel := observability{} + diags := optionsModel.Observability.As(ctx, &observabilityModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting observability: %w", core.DiagsToError(diags)) + } + + // observability logs + observabilityLogsModel := observabilityOption{} + diags = observabilityModel.Logs.As(ctx, &observabilityLogsModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting observability logs: %w", core.DiagsToError(diags)) + } + observabilityPayload.Logs = &loadbalancer.LoadbalancerOptionLogs{ + CredentialsRef: observabilityLogsModel.CredentialsRef.ValueStringPointer(), + PushUrl: observabilityLogsModel.PushUrl.ValueStringPointer(), + } + + // observability metrics + observabilityMetricsModel := observabilityOption{} + diags = observabilityModel.Metrics.As(ctx, &observabilityMetricsModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting observability metrics: %w", core.DiagsToError(diags)) + } + observabilityPayload.Metrics = &loadbalancer.LoadbalancerOptionMetrics{ + CredentialsRef: observabilityMetricsModel.CredentialsRef.ValueStringPointer(), + PushUrl: observabilityMetricsModel.PushUrl.ValueStringPointer(), + } + } + payload := loadbalancer.LoadBalancerOptions{ AccessControl: accessControlPayload, + Observability: observabilityPayload, PrivateNetworkOnly: conversion.BoolValueToPointer(optionsModel.PrivateNetworkOnly), } @@ -1249,6 +1356,42 @@ func mapOptions(ctx context.Context, loadBalancerResp *loadbalancer.LoadBalancer return fmt.Errorf("mapping field ACL: %w", err) } + observabilityLogsMap := map[string]attr.Value{ + "credentials_ref": types.StringNull(), + "push_url": types.StringNull(), + } + if loadBalancerResp.Options.HasObservability() && loadBalancerResp.Options.Observability.HasLogs() { + observabilityLogsMap["credentials_ref"] = types.StringPointerValue(loadBalancerResp.Options.Observability.Logs.CredentialsRef) + observabilityLogsMap["push_url"] = types.StringPointerValue(loadBalancerResp.Options.Observability.Logs.PushUrl) + } + observabilityLogsTF, diags := types.ObjectValue(observabilityOptionTypes, observabilityLogsMap) + if diags.HasError() { + return core.DiagsToError(diags) + } + + observabilityMetricsMap := map[string]attr.Value{ + "credentials_ref": types.StringNull(), + "push_url": types.StringNull(), + } + if loadBalancerResp.Options.HasObservability() && loadBalancerResp.Options.Observability.HasMetrics() { + observabilityMetricsMap["credentials_ref"] = types.StringPointerValue(loadBalancerResp.Options.Observability.Metrics.CredentialsRef) + observabilityMetricsMap["push_url"] = types.StringPointerValue(loadBalancerResp.Options.Observability.Metrics.PushUrl) + } + observabilityMetricsTF, diags := types.ObjectValue(observabilityOptionTypes, observabilityMetricsMap) + if diags.HasError() { + return core.DiagsToError(diags) + } + + observabilityMap := map[string]attr.Value{ + "logs": observabilityLogsTF, + "metrics": observabilityMetricsTF, + } + observabilityTF, diags := types.ObjectValue(observabilityTypes, observabilityMap) + if diags.HasError() { + return core.DiagsToError(diags) + } + optionsMap["observability"] = observabilityTF + optionsTF, diags := types.ObjectValue(optionsTypes, optionsMap) if diags.HasError() { return core.DiagsToError(diags) diff --git a/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go b/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go index 95a85018..cb41abce 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go @@ -32,6 +32,7 @@ func TestToCreatePayload(t *testing.T) { AllowedSourceRanges: nil, }, PrivateNetworkOnly: nil, + Observability: &loadbalancer.LoadbalancerOptionObservability{}, }, TargetPools: nil, }, @@ -76,6 +77,16 @@ func TestToCreatePayload(t *testing.T) { types.StringType, []attr.Value{types.StringValue("cidr")}), "private_network_only": types.BoolValue(true), + "observability": types.ObjectValueMust(observabilityTypes, map[string]attr.Value{ + "logs": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringValue("logs-credentials_ref"), + "push_url": types.StringValue("logs-push_url"), + }), + "metrics": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringValue("metrics-credentials_ref"), + "push_url": types.StringValue("metrics-push_url"), + }), + }), }, ), TargetPools: types.ListValueMust(types.ObjectType{AttrTypes: targetPoolTypes}, []attr.Value{ @@ -132,6 +143,16 @@ func TestToCreatePayload(t *testing.T) { AllowedSourceRanges: &[]string{"cidr"}, }, PrivateNetworkOnly: utils.Ptr(true), + Observability: &loadbalancer.LoadbalancerOptionObservability{ + Logs: &loadbalancer.LoadbalancerOptionLogs{ + CredentialsRef: utils.Ptr("logs-credentials_ref"), + PushUrl: utils.Ptr("logs-push_url"), + }, + Metrics: &loadbalancer.LoadbalancerOptionMetrics{ + CredentialsRef: utils.Ptr("metrics-credentials_ref"), + PushUrl: utils.Ptr("metrics-push_url"), + }, + }, }, TargetPools: &[]loadbalancer.TargetPool{ { @@ -290,6 +311,10 @@ func TestMapFields(t *testing.T) { AllowedSourceRanges: nil, }, PrivateNetworkOnly: nil, + Observability: &loadbalancer.LoadbalancerOptionObservability{ + Logs: &loadbalancer.LoadbalancerOptionLogs{}, + Metrics: &loadbalancer.LoadbalancerOptionMetrics{}, + }, }, TargetPools: nil, }, @@ -305,6 +330,16 @@ func TestMapFields(t *testing.T) { Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{ "acl": types.SetNull(types.StringType), "private_network_only": types.BoolNull(), + "observability": types.ObjectValueMust(observabilityTypes, map[string]attr.Value{ + "logs": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringNull(), + "push_url": types.StringNull(), + }), + "metrics": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringNull(), + "push_url": types.StringNull(), + }), + }), }), PrivateAddress: types.StringNull(), TargetPools: types.ListNull(types.ObjectType{AttrTypes: targetPoolTypes}), @@ -342,6 +377,16 @@ func TestMapFields(t *testing.T) { }), Options: utils.Ptr(loadbalancer.LoadBalancerOptions{ PrivateNetworkOnly: utils.Ptr(true), + Observability: &loadbalancer.LoadbalancerOptionObservability{ + Logs: &loadbalancer.LoadbalancerOptionLogs{ + CredentialsRef: utils.Ptr("logs_credentials_ref"), + PushUrl: utils.Ptr("logs_push_url"), + }, + Metrics: &loadbalancer.LoadbalancerOptionMetrics{ + CredentialsRef: utils.Ptr("metrics_credentials_ref"), + PushUrl: utils.Ptr("metrics_push_url"), + }, + }, }), TargetPools: utils.Ptr([]loadbalancer.TargetPool{ { @@ -405,6 +450,16 @@ func TestMapFields(t *testing.T) { map[string]attr.Value{ "private_network_only": types.BoolValue(true), "acl": types.SetNull(types.StringType), + "observability": types.ObjectValueMust(observabilityTypes, map[string]attr.Value{ + "logs": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringValue("logs_credentials_ref"), + "push_url": types.StringValue("logs_push_url"), + }), + "metrics": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringValue("metrics_credentials_ref"), + "push_url": types.StringValue("metrics_push_url"), + }), + }), }, ), TargetPools: types.ListValueMust(types.ObjectType{AttrTypes: targetPoolTypes}, []attr.Value{ @@ -466,6 +521,16 @@ func TestMapFields(t *testing.T) { AllowedSourceRanges: utils.Ptr([]string{"cidr"}), }, PrivateNetworkOnly: nil, // API sets this to nil if it's false in the request + Observability: &loadbalancer.LoadbalancerOptionObservability{ + Logs: &loadbalancer.LoadbalancerOptionLogs{ + CredentialsRef: utils.Ptr("logs_credentials_ref"), + PushUrl: utils.Ptr("logs_push_url"), + }, + Metrics: &loadbalancer.LoadbalancerOptionMetrics{ + CredentialsRef: utils.Ptr("metrics_credentials_ref"), + PushUrl: utils.Ptr("metrics_push_url"), + }, + }, }), TargetPools: utils.Ptr([]loadbalancer.TargetPool{ { @@ -531,6 +596,16 @@ func TestMapFields(t *testing.T) { types.StringType, []attr.Value{types.StringValue("cidr")}), "private_network_only": types.BoolValue(false), + "observability": types.ObjectValueMust(observabilityTypes, map[string]attr.Value{ + "logs": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringValue("logs_credentials_ref"), + "push_url": types.StringValue("logs_push_url"), + }), + "metrics": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringValue("metrics_credentials_ref"), + "push_url": types.StringValue("metrics_push_url"), + }), + }), }, ), TargetPools: types.ListValueMust(types.ObjectType{AttrTypes: targetPoolTypes}, []attr.Value{ @@ -585,6 +660,16 @@ func TestMapFields(t *testing.T) { model.Options = types.ObjectValueMust(optionsTypes, map[string]attr.Value{ "private_network_only": types.BoolValue(*tt.modelPrivateNetworkOnly), "acl": types.SetNull(types.StringType), + "observability": types.ObjectValueMust(observabilityTypes, map[string]attr.Value{ + "logs": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringNull(), + "push_url": types.StringNull(), + }), + "metrics": types.ObjectValueMust(observabilityOptionTypes, map[string]attr.Value{ + "credentials_ref": types.StringNull(), + "push_url": types.StringNull(), + }), + }), }) } err := mapFields(context.Background(), tt.input, model, tt.region) diff --git a/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go b/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go index d99854b7..4fe3d9e0 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go +++ b/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go @@ -67,6 +67,15 @@ var testConfigVarsMax = config.Variables{ "use_source_ip_address": config.StringVariable("true"), "private_network_only": config.StringVariable("false"), "acl": config.StringVariable("192.168.0.0/24"), + + "observability_logs_push_url": config.StringVariable("https://logs.observability.dummy.stackit.cloud"), + "observability_metrics_push_url": config.StringVariable("https://metrics.observability.dummy.stackit.cloud"), + "observability_credential_logs_name": config.StringVariable(fmt.Sprintf("tf-acc-l%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "observability_credential_logs_username": config.StringVariable("obs-cred-logs-username"), + "observability_credential_logs_password": config.StringVariable("obs-cred-logs-password"), + "observability_credential_metrics_name": config.StringVariable(fmt.Sprintf("tf-acc-m%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "observability_credential_metrics_username": config.StringVariable("obs-cred-metrics-username"), + "observability_credential_metrics_password": config.StringVariable("obs-cred-metrics-password"), } func configVarsMinUpdated() config.Variables { @@ -93,7 +102,7 @@ func TestAccLoadBalancerResourceMin(t *testing.T) { ConfigVariables: testConfigVarsMin, Config: testutil.LoadBalancerProviderConfig() + resourceMinConfig, Check: resource.ComposeAggregateTestCheckFunc( - // Load balancer instance + // Load balancer instance resource resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "name", testutil.ConvertConfigVariable(testConfigVarsMin["loadbalancer_name"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.name", testutil.ConvertConfigVariable(testConfigVarsMin["target_pool_name"])), @@ -107,12 +116,17 @@ func TestAccLoadBalancerResourceMin(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "networks.0.role", testutil.ConvertConfigVariable(testConfigVarsMin["network_role"])), resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "external_address"), + resource.TestCheckNoResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), + resource.TestCheckNoResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.logs.push_url"), + resource.TestCheckNoResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), + resource.TestCheckNoResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.metrics.push_url"), - // observability credentials + // Loadbalancer observability credentials resource resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.obs_credential", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.obs_credential", "display_name", testutil.ConvertConfigVariable(testConfigVarsMin["obs_display_name"])), resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.obs_credential", "username", testutil.ConvertConfigVariable(testConfigVarsMin["obs_username"])), resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.obs_credential", "password", testutil.ConvertConfigVariable(testConfigVarsMin["obs_password"])), + resource.TestCheckResourceAttrSet("stackit_loadbalancer_observability_credential.obs_credential", "credentials_ref"), ), }, // Data source @@ -151,6 +165,10 @@ func TestAccLoadBalancerResourceMin(t *testing.T) { resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "networks.0.role", testutil.ConvertConfigVariable(testConfigVarsMin["network_role"])), resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "external_address"), + resource.TestCheckNoResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), + resource.TestCheckNoResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.logs.push_url"), + resource.TestCheckNoResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), + resource.TestCheckNoResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.push_url"), )}, // Import { @@ -197,7 +215,7 @@ func TestAccLoadBalancerResourceMax(t *testing.T) { ConfigVariables: testConfigVarsMax, Config: testutil.LoadBalancerProviderConfig() + resourceMaxConfig, Check: resource.ComposeAggregateTestCheckFunc( - // Load balancer instance + // Load balancer instance resource resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "name", testutil.ConvertConfigVariable(testConfigVarsMax["loadbalancer_name"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.name", testutil.ConvertConfigVariable(testConfigVarsMax["target_pool_name"])), @@ -222,6 +240,25 @@ func TestAccLoadBalancerResourceMax(t *testing.T) { resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.session_persistence.use_source_ip_address", testutil.ConvertConfigVariable(testConfigVarsMax["use_source_ip_address"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "options.private_network_only", testutil.ConvertConfigVariable(testConfigVarsMax["private_network_only"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "options.acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl"])), + + resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), + resource.TestCheckResourceAttrPair("stackit_loadbalancer_observability_credential.logs", "credentials_ref", "stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.logs.push_url", testutil.ConvertConfigVariable(testConfigVarsMax["observability_logs_push_url"])), + resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), + resource.TestCheckResourceAttrPair("stackit_loadbalancer_observability_credential.metrics", "credentials_ref", "stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "options.observability.metrics.push_url", testutil.ConvertConfigVariable(testConfigVarsMax["observability_metrics_push_url"])), + + // Loadbalancer observability credential resource + resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.logs", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.logs", "display_name", testutil.ConvertConfigVariable(testConfigVarsMax["observability_credential_logs_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.logs", "username", testutil.ConvertConfigVariable(testConfigVarsMax["observability_credential_logs_username"])), + resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.logs", "password", testutil.ConvertConfigVariable(testConfigVarsMax["observability_credential_logs_password"])), + resource.TestCheckResourceAttrSet("stackit_loadbalancer_observability_credential.logs", "credentials_ref"), + resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.metrics", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.metrics", "display_name", testutil.ConvertConfigVariable(testConfigVarsMax["observability_credential_metrics_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.metrics", "username", testutil.ConvertConfigVariable(testConfigVarsMax["observability_credential_metrics_username"])), + resource.TestCheckResourceAttr("stackit_loadbalancer_observability_credential.metrics", "password", testutil.ConvertConfigVariable(testConfigVarsMax["observability_credential_metrics_password"])), + resource.TestCheckResourceAttrSet("stackit_loadbalancer_observability_credential.metrics", "credentials_ref"), ), }, // Data source @@ -271,6 +308,13 @@ func TestAccLoadBalancerResourceMax(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.session_persistence.use_source_ip_address", testutil.ConvertConfigVariable(testConfigVarsMax["use_source_ip_address"])), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl"])), + + resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), + resource.TestCheckResourceAttrPair("stackit_loadbalancer_observability_credential.logs", "credentials_ref", "data.stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.logs.push_url", testutil.ConvertConfigVariable(testConfigVarsMax["observability_logs_push_url"])), + resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), + resource.TestCheckResourceAttrPair("stackit_loadbalancer_observability_credential.metrics", "credentials_ref", "data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.credentials_ref"), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.observability.metrics.push_url", testutil.ConvertConfigVariable(testConfigVarsMax["observability_metrics_push_url"])), )}, // Import { diff --git a/stackit/internal/services/loadbalancer/testfiles/resource-max.tf b/stackit/internal/services/loadbalancer/testfiles/resource-max.tf index 4fc7aefb..2b9ba616 100644 --- a/stackit/internal/services/loadbalancer/testfiles/resource-max.tf +++ b/stackit/internal/services/loadbalancer/testfiles/resource-max.tf @@ -22,6 +22,15 @@ variable "use_source_ip_address" {} variable "private_network_only" {} variable "acl" {} +variable "observability_logs_push_url" {} +variable "observability_metrics_push_url" {} +variable "observability_credential_logs_name" {} +variable "observability_credential_logs_username" {} +variable "observability_credential_logs_password" {} +variable "observability_credential_metrics_name" {} +variable "observability_credential_metrics_username" {} +variable "observability_credential_metrics_password" {} + resource "stackit_network" "network" { project_id = var.project_id name = var.network_name @@ -108,6 +117,31 @@ resource "stackit_loadbalancer" "loadbalancer" { options = { private_network_only = var.private_network_only acl = [var.acl] + observability = { + logs = { + credentials_ref = stackit_loadbalancer_observability_credential.logs.credentials_ref + push_url = var.observability_logs_push_url + } + metrics = { + credentials_ref = stackit_loadbalancer_observability_credential.metrics.credentials_ref + push_url = var.observability_metrics_push_url + } + } } external_address = stackit_public_ip.public_ip.ip } + +resource "stackit_loadbalancer_observability_credential" "logs" { + project_id = var.project_id + display_name = var.observability_credential_logs_name + username = var.observability_credential_logs_username + password = var.observability_credential_logs_password +} + +resource "stackit_loadbalancer_observability_credential" "metrics" { + project_id = var.project_id + display_name = var.observability_credential_metrics_name + username = var.observability_credential_metrics_username + password = var.observability_credential_metrics_password +} + diff --git a/templates/guides/using_loadbalancer_with_observability.md.tmpl b/templates/guides/using_loadbalancer_with_observability.md.tmpl new file mode 100644 index 00000000..106d8797 --- /dev/null +++ b/templates/guides/using_loadbalancer_with_observability.md.tmpl @@ -0,0 +1,167 @@ +--- +page_title: "Using the STACKIT Loadbalancer together with STACKIT Observability" +--- +# Using the STACKIT Loadbalancer together with STACKIT Observability + +## Overview + +This guide explains how to configure the STACKIT Loadbalancer product to send metrics and logs to a STACKIT Observability instance. + +1. **Set Up Providers** + + Begin by configuring the STACKIT provider to connect to the STACKIT services. + + ```hcl + provider "stackit" { + default_region = "eu01" + } + ``` + +2. **Create an Observability instance** + + Establish a STACKIT Observability instance and its credentials. + + ```hcl + resource "stackit_observability_instance" "observability01" { + project_id = var.project_id_prod + name = "example-instance" + plan_name = "Observability-Monitoring-Medium-EU01" + acl = ["0.0.0.0/0"] + metrics_retention_days = 30 + metrics_retention_days_5m_downsampling = 10 + metrics_retention_days_1h_downsampling = 5 + } + + resource "stackit_observability_credential" "observability01-credential" { + project_id = var.project_id_prod + instance_id = stackit_observability_instance.observability01.instance_id + } + ``` + +3. **Create STACKIT Loadbalancer credentials reference** + + Create a STACKIT Loadbalancer credentials which will be used in the STACKIT Loadbalancer resource as a reference. + + ```hcl + resource "stackit_loadbalancer_observability_credential" "example" { + project_id = var.project_id_prod + display_name = "example-credentials" + username = stackit_observability_credential.observability01-credential.username + password = stackit_observability_credential.observability01-credential.password + } + ``` + +4. **Create the STACKIT Loadbalancer** + + ```hcl + # Create a network + resource "stackit_network" "example_network" { + project_id = var.project_id_prod + name = "example-network" + ipv4_nameservers = ["8.8.8.8"] + ipv4_prefix = "192.168.0.0/25" + labels = { + "key" = "value" + } + routed = true + } + + # Create a network interface + resource "stackit_network_interface" "nic" { + project_id = var.project_id_prod + network_id = stackit_network.example_network.network_id + } + + # Create a public IP for the load balancer + resource "stackit_public_ip" "public-ip" { + project_id = var.project_id_prod + lifecycle { + ignore_changes = [network_interface_id] + } + } + + # Create a key pair for accessing the server instance + resource "stackit_key_pair" "keypair" { + name = "example-key-pair" + # set the path of your public key file here + public_key = chomp(file("/home/bob/.ssh/id_ed25519.pub")) + } + + # Create a server instance + resource "stackit_server" "boot-from-image" { + project_id = var.project_id_prod + name = "example-server" + boot_volume = { + size = 64 + source_type = "image" + source_id = "59838a89-51b1-4892-b57f-b3caf598ee2f" // Ubuntu 24.04 + } + availability_zone = "eu01-1" + machine_type = "g1.1" + keypair_name = stackit_key_pair.keypair.name + } + + # Attach the network interface to the server + resource "stackit_server_network_interface_attach" "nic-attachment" { + project_id = var.project_id_prod + server_id = stackit_server.boot-from-image.server_id + network_interface_id = stackit_network_interface.nic.network_interface_id + } + + # Create a load balancer + resource "stackit_loadbalancer" "example" { + project_id = var.project_id_prod + name = "example-load-balancer" + target_pools = [ + { + name = "example-target-pool" + target_port = 80 + targets = [ + { + display_name = stackit_server.boot-from-image.name + ip = stackit_network_interface.nic.ipv4 + } + ] + active_health_check = { + healthy_threshold = 10 + interval = "3s" + interval_jitter = "3s" + timeout = "3s" + unhealthy_threshold = 10 + } + } + ] + listeners = [ + { + display_name = "example-listener" + port = 80 + protocol = "PROTOCOL_TCP" + target_pool = "example-target-pool" + } + ] + networks = [ + { + network_id = stackit_network.example_network.network_id + role = "ROLE_LISTENERS_AND_TARGETS" + } + ] + external_address = stackit_public_ip.public-ip.ip + options = { + private_network_only = false + observability = { + logs = { + # uses the load balancer credential from the last step + credentials_ref = stackit_loadbalancer_observability_credential.example.credentials_ref + # uses the observability instance from step 1 + push_url = stackit_observability_instance.observability01.logs_push_url + } + metrics = { + # uses the load balancer credential from the last step + credentials_ref = stackit_loadbalancer_observability_credential.example.credentials_ref + # uses the observability instance from step 1 + push_url = stackit_observability_instance.observability01.metrics_push_url + } + } + } + } + ```