Iniciando os scripts via Terraform para Netscaler ADC - Parte 2

Olá Pessoal,

Conforme explicado nesse post a idéia seria iniciarmos as tratativas dos arquivos de configuração para fazer o deploy de algum recurso em nosso Netscaler ADC.

Vale ressaltar, para quem não visualizou nosso post anterior, onde falamos como estruturar o projeto pode conferir nesse link.

Configurar LB and CS no Netscaler ADC

Lembrando que não vou explicar as funcionalidades expostas nesses scripts, por exemplo, como funciona LB(Load Balancer) ou CS(Content Switching), pois acaba ficando fora do contexto sobre o processo de automação nesse momento, porém posteriormente posso ir detalhando os recursos com as explicações de cada funcionalidade.

Para inciarmos como explicado anteriormente precisamos configurar o arquivo provider.tf. Segue o snippet:

terraform {
 required_providers {
   citrixadc = {
     source  = "citrix/citrixadc"
     version = "1.35.0"
   }
 }
}

provider "citrixadc" {
  # Configuration options
  endpoint = "http://IP-or-URL-accesso-ao-Netscaler"
  username = "user"
  password = "pwd"

  # Do not error due to non signed ADC TLS certificate
  # Can skip this if ADC TLS certificate is trusted
  insecure_skip_verify = true
}

Obs.: Devido a ser um ambiente de laboratório não estamos executando o acesso via https, bem como eu habilitei para fazer o skip . Bem como, a versão do registry foi a testada em laboratório e pode ser utilizado a versão mais nova se desejarem.

Na sequência precisamos definir quais serão as variáveis que serão consumidas pelo nosso scripts, ou seja, os valores/strings que devem ser inseridos em nosso arquivo de recursos. Nesse caso foi criado o arquivo variables.tf . Segue abaixo:

# Start variable based in global configuration of LB Server
variable "vip_config" {
  type        = map(string)
  description = "Describes the friendly name (key=lbname),VIP(key=vip), port(key=port) and service type (key=servicetype) for the LB"
}

variable "lb_methods" {
  type        = map(string)
  description = "Configure different methods of balacing in Vserver"
}

variable "monitor_config" {
  type = map(string)
}

variable "backend_service_config" {
  type        = map(string)
  description = "Describes port(key=port) and service type (key=servicetype) for the backend services"
}

variable "backend_services" {
  description = "The list of backend services (ip address:port list)"
  type        = list(string)
}

# Start variables of all cs server configuration
variable "csvserver_name" {
  type        = string
  description = "CS vserver name"
}
variable "csvserver_ipv46" {
  type        = string
  description = "CS vserver ip"
}
variable "csvserver_servicetype" {
  description = "CS vserver Servicetype"
}
variable "csvserver_port" {
  type        = number
  description = "CS vserver Port"
}

variable "cspolicy1_name" {
  type        = string
  description = "CS policy1 name"
}
variable "cspolicy1_rule" {
  description = "CS Policy1 Url"
}

variable "cspolicy2_name" {
  type        = string
  description = "CS policy2 name"
}
variable "cspolicy2_rule" {
  description = "CS Policy2 Url"
}

variable "cspolicy3_name" {
  type        = string
  description = "CS policy3 name"
}
variable "cspolicy3_rule" {
  description = "CS Policy3 Url"
}

variable "cspolicy4_name" {
  type        = string
  description = "CS policy4 name"
}
variable "cspolicy4_rule" {
  description = "CS Policy4 Url"
}
variable "sslcertkey_name" {
  type        = string
  description = "SSL CertKey Attribute"
}
variable "sslcertkey_cert" {
  description = "SSL Cert Attribute"
}
variable "sslcertkey_key" {
  description = "SSL Key Attribute"
}

Obs.: Lembrando que os nomes das variáveis pode ser escolhidos da forma que desejarem e não precisam seguir como meu exemplo, bem como as descrições. A obrigatoriedade é seguir conforme a variável sendo um string, number, mapeamento de string.

Após a criação das variáveis iremos agora popular os valores que cada uma delas irá ser populada em nosso Netscaler ADC. O arquivo criado seria terraform.tfvars e pode ser verificado conforme abaixo:

# General Variables
vip_config = {
  vip_service   = "10.1.51.1"
  vip_servgroup = "10.1.51.2"
  vip_csserver  = "10.1.51.3"
}

lb_methods = {
    main   = "LEASTPACKETS"
    backup = "LEASTRESPONSETIME"
}

backend_service_config = {
  clttimeout   = 40
  backend_port = 80
}

backend_services = [
  "10.0.5.1",
  "10.0.6.1",
  "10.0.7.1",
]

monitor_config = {
  name                = "tf_monitor_sip-udp"
  interval_ms         = 150
  response_timeout_ms = 50
}
#
# CS Vserver
csvserver_name        = "tf_vserver-cs-SSL"
csvserver_ipv46       = "10.1.60.1"
csvserver_port        = 443
csvserver_servicetype = "SSL"

# CS Policy 1
cspolicy1_name  = "pol1"
cspolicy1_rule  = "HTTP.REQ.URL.SUFFIX.EQ(\"cgi\")"

# CS Policy 2
cspolicy2_name  = "pol2"
cspolicy2_rule  = "HTTP.REQ.URL.SUFFIX.EQ(\"asp\")"

# CS Policy 3
cspolicy3_name  = "pol3"
cspolicy3_rule  = "HTTP.REQ.URL.SUFFIX.EQ(\"gif\")"

# CS Policy 4
cspolicy4_name  = "pol4"
cspolicy4_rule  = "HTTP.REQ.URL.SUFFIX.EQ(\"jpeg\")"

# SSL CertKey
sslcertkey_name = "mykey"
sslcertkey_cert = "/nsconfig/ssl/ns-root.cert"
sslcertkey_key  = "/nsconfig/ssl/ns-root.key"

Obs.: Novamente os endereços populados ficam a critério de cada estrutura, bem como as politicas que eu criei dentro do content switching.

Finalmente iremos agora consumir todos esses scripts através de nosso arquivo resources.tf que será a partir dele que será endereçado todos os outros parâmetros definidos. Segue abaixo:

# Adding monitor specific to binding in LB-Server
resource "citrixadc_lbmonitor" "generic_monitor" {
  monitorname = var.monitor_config["name"]
  type        = "SIP-UDP"
  interval    = var.monitor_config["interval_ms"]
  resptimeout = var.monitor_config["response_timeout_ms"]
  sipmethod   = "INVITE"
  sipuri      = "sip:sip.test"
  respcode    = [400]
}

# Configure Service
resource "citrixadc_service" "tf_service" {
  name         = "tf_service_1"
  ip           = "10.1.50.1"
  servicetype  = "HTTP"
  port         = 80
  lbmonitor    = citrixadc_lbmonitor.generic_monitor.monitorname
}

# Configure LB Server and parameters
resource "citrixadc_lbvserver" "tf_lb1" {
  name            = "tf_lb_vserver_1"
  ipv46           = var.vip_config["vip_service"]
  port            = "80"
  lbmethod        = var.lb_methods["main"]
  backuplbmethod  = var.lb_methods["backup"]
  servicetype     = "HTTP"
  persistencetype = "SOURCEIP"
  tcpprofilename  = "nstcp_internal_apps"
}

# Configure Binding of Service to LB Server
resource "citrixadc_lbvserver_service_binding" "tf_binding" {
  name = citrixadc_lbvserver.tf_lb1.name
  servicename = citrixadc_service.tf_service.name
  weight = 5
}

# Configure LB Serv-Group and parameters
resource "citrixadc_lbvserver" "tf_lb2" {
  name           = "tf_lb_vserv_group_1"
  ipv46          = var.vip_config["vip_servgroup"]
  port           = "80"
  lbmethod       = var.lb_methods["main"]
  backuplbmethod = var.lb_methods["backup"]
  servicetype    = "HTTP"
}

# Configure Binding of Service Group
resource "citrixadc_servicegroup" "backend" {
  servicegroupname = "tf_serv_group_backend"
  lbvservers       = [citrixadc_lbvserver.tf_lb2.name]
  servicetype      = "HTTP"
  usip             = "NO"
  clttimeout       = var.backend_service_config["clttimeout"]
  servicegroupmembers = formatlist(
    "%s:%s",
    var.backend_services,
    var.backend_service_config["backend_port"],
  )
  comment          = "Inserted via Terraform"
}

# Define CS Server
resource "citrixadc_service" "tf_cs_service" {
  name         = "tf_cs_service_1"
  ip           = "10.1.50.2"
  servicetype  = "HTTP"
  port         = 80
}

resource "citrixadc_lbvserver" "tf_cs_lb1" {
  name            = "tf_lb_cs_vserver_1"
  ipv46           = var.vip_config["vip_csserver"]
  port            = "80"
  lbmethod        = var.lb_methods["main"]
  backuplbmethod  = var.lb_methods["backup"]
  servicetype     = "HTTP"
  state           = "ENABLED"
}

resource "citrixadc_lbvserver_service_binding" "tf_cs_binding" {
  name = citrixadc_lbvserver.tf_cs_lb1.name
  servicename = citrixadc_service.tf_cs_service.name
  weight = 5
}

resource "citrixadc_csvserver" "tf_csvserver" {
  name        = var.csvserver_name
  ipv46       = var.csvserver_ipv46
  port        = var.csvserver_port
  servicetype = var.csvserver_servicetype
  state       = "ENABLED"
  stateupdate = "ENABLED"
}

resource "citrixadc_csaction" "csaction_html" {
  name            = "csaction_HTML"
  targetlbvserver = citrixadc_lbvserver.tf_cs_lb1.name
}
resource "citrixadc_csaction" "csaction_image" {
  name            = "csaction_Image"
  targetlbvserver = citrixadc_lbvserver.tf_cs_lb1.name
}
resource "citrixadc_cspolicy" "tf_cspolicy1" {
  policyname = var.cspolicy1_name
  rule       = var.cspolicy1_rule
  action     = citrixadc_csaction.csaction_html.name
}
resource "citrixadc_cspolicy" "tf_cspolicy2" {
  policyname = var.cspolicy2_name
  rule       = var.cspolicy2_rule
  action     = citrixadc_csaction.csaction_html.name
}
resource "citrixadc_cspolicy" "tf_cspolicy3" {
  policyname = var.cspolicy3_name
  rule       = var.cspolicy3_rule
  action     = citrixadc_csaction.csaction_image.name
}
resource "citrixadc_cspolicy" "tf_cspolicy4" {
  policyname = var.cspolicy4_name
  rule       = var.cspolicy4_rule
  action     = citrixadc_csaction.csaction_image.name
}

resource "citrixadc_csvserver_cspolicy_binding" "tf_bind1" {
  name       = citrixadc_csvserver.tf_csvserver.name
  policyname = citrixadc_cspolicy.tf_cspolicy1.policyname
  priority   = 100
}
resource "citrixadc_csvserver_cspolicy_binding" "tf_bind2" {
  name       = citrixadc_csvserver.tf_csvserver.name
  policyname = citrixadc_cspolicy.tf_cspolicy2.policyname
  priority   = 200
}
resource "citrixadc_csvserver_cspolicy_binding" "tf_bind3" {
  name       = citrixadc_csvserver.tf_csvserver.name
  policyname = citrixadc_cspolicy.tf_cspolicy3.policyname
  priority   = 300
}
resource "citrixadc_csvserver_cspolicy_binding" "tf_bind4" {
  name       = citrixadc_csvserver.tf_csvserver.name
  policyname = citrixadc_cspolicy.tf_cspolicy4.policyname
  priority   = 400
}

resource "citrixadc_sslcertkey" "tf_sslcertkey" {
  certkey = var.sslcertkey_name
  cert    = var.sslcertkey_cert
  key     = var.sslcertkey_key
}

resource "citrixadc_sslvserver_sslcertkey_binding" "tf_binding" {
  vservername = citrixadc_csvserver.tf_csvserver.name
  certkeyname = citrixadc_sslcertkey.tf_sslcertkey.certkey
}

Execução do script via terminal

Após todo esse trabalho desejamos que através de um único comando possamos executar toda essa configuração rapidamente, ou seja, minimizando o tempo deploy.

Para isso necessitamos acessar nosso projeto que foram populados todos esses arquivos e executar alguns comandos no Terraform. Os comandos abaixo são os básicos para o deploy e posteriormente a destruição de sua configuração.

# Inicia todo o projeto compilando o pacote do register
$ terraform init

# Verifica se possivelmente existe erros na sua configuração e quais os campos estão sendo preenchidos no Netscaler ADC
$ terraform plan

# Executa a configuração no seu Netscaler ADC
$ terraform apply

Obs.: Apenas relembrando para que você consiga executar esse procedimento a sua maquina local, precisa ter acesso diretamente ao seu Netscaler ADC.

Visualização dos procedimentos

Logo abaixo eu compartilho algumas telas apenas para demonstração da execução dos comandos mencionados anteriormente.

  1. Terraform Init

Execução do Terraform Init

  1. Terraform Plan

Execução do Terraform Plan

  1. Terraform Apply

Execução do Terraform Apply

Obs.: Neste último printscreen eu obtive o resultado final apenas para demonstrar sobre o status que o terrafom apresenta como tendo recursos adicionados, modificados e destruídos.

  1. Visualização na GUI do Netscaler ADC

GUI do Netscaler ADC

Apenas um breve printscreen da tela do Netscaler ADC com os LBs configurados baseado em nosso arquivo de configuração.

Conclusão

Acredito que ficou extenso o post devido aos arquivos de configuração, porém achei importante nesse primeiro momento para que possa demonstrar as opções que temos para executar nossa famosa automação.

Logos após nossos posts irei disponibilizar no github todos esses arquivos para ficarem mais fácil para clonar o repositório.

Atte,

Rodrigo