В першій частині розібрались з основами AWS OpenSearch Service взагалі, і з типами інстансів для Data Nodes – AWS: знайомство з OpenSearch Service в ролі vector store.
В другій – з доступами, AWS: створення OpenSearch Service cluster та налаштування аутентифікації і авторизації.
Тепер напишемо Terraform code для створення кластера, юзерів та індексів.
Створювати кластер будемо в VPC, для аутентифікації використаємо internal user database.
А в VPC не можна… Бо – suprize! – AWS Bedrock вимагає OpenSeach Managed кластер саме Public, а не в VPC.
The OpenSearch Managed Cluster you provided is not supported because it is VPC protected. Your cluster must be behind a public network.
Писав в сапорт, сказали, що:
However, there is an ongoing product feature request (PFR) to have Bedrock KnowledgeBases support provisioned Open Search clusters in VPC.
І пропонують використати Amazon OpenSearch Serverless, з якого ми власне і тікаємо, бо ціни дурні.
Друга проблема, яка виявилась, коли я почав писати ресурси bedrockagent_knowledge_base – це те, що він не підтримує storage_configuration з type == OPENSEARCH_MANAGED, тільки Serverless.
Але Pull Request на це вже є, колись, може, замержать.
Отже, будемо робити OpenSearch Managed Service кластер, кластер буде один, з трьома індексами – Dev/Staging/Prod.
В кластері буде три маленькі дата-ноди, а в кожному індексі – 1 primary shard та 1 репліка, бо проект маленький, даних в нашому Production індексі на AWS OpenSearch Serverless, з якого ми хочемо переїхати на AWS OpenSearch Service – зараз всього 2 GiB, і навряд чи в майбутньому буде дуже багато.
Було б добре кластер зробити у власному Terraform модулі аби простіше створювати якісь тестові оточення, як в мене це зроблено для AWS EKS – але поки не дуже є на це час, тому робимо просто tf-файлами з окремим prod.tfvars для змінних.
Може, потім напишу окремо по переносу у власний модуль, бо це дійсно зручно.
І в наступній частині – поговоримо про моніторинг, бо наш Production вже разок падав 🙂
Зміст
Структура Terraform файлів
Початкова схема файлів і директорій проекту така:
$ tree .
.
├── README.md
└── terraform
├── Makefile
├── backend.tf
├── data.tf
├── envs
│ └── prod
│ └── prod.tfvars
├── locals.tf
├── outputs.tf
├── providers.tf
├── variables.tf
└── versions.tf
В providers.tf – налаштування провайдерів, тут поки тільки AWS, і через нього задаємо дефолтні теги:
provider "aws" {
region = var.aws_region
default_tags {
tags = {
component = var.component
created-by = "terraform"
environment = var.environment
}
}
}
В data.tf збираємо дані AWS Account ID, Availability Zones, VPC та приватні subnets, в яких будемо створювати кластер в яких колись потім будемо створювати кластер:
data "aws_caller_identity" "current" {}
data "aws_availability_zones" "available" {
state = "available"
}
data "aws_vpc" "eks_vpc" {
id = var.vpc_id
}
data "aws_subnets" "private" {
filter {
name = "vpc-id"
values = [var.vpc_id]
}
tags = {
subnet-type = "private"
}
}
Файл variables.tf з нашими дефолтними змінними, потім будемо додавати нові:
variable "aws_region" {
type = string
}
variable "project_name" {
description = "A project name to be used in resources"
type = string
}
variable "component" {
description = "A team using this project (backend, web, ios, data, devops)"
type = string
}
variable "environment" {
description = "Dev/Prod, will be used in AWS resources Name tag, and resources names"
type = string
}
variable "vpc_id" {
type = string
description = "A VPC ID to be used to create OpenSearch cluster and its Nodes"
}
Значення змінних передаємо через окремий prod.tfvars, потім, при потребі, можна буде створити нове оточення через файл типу envs/test/test.tfvars:
aws_region = "us-east-1" project_name = "atlas-kb" component = "backend" environment = "prod" vpc_id = "vpc-0fbaffe234c0d81ea" dns_zone = "prod.example.co"
В Makefile – спрощуємо собі локальне життя:
############ ### PROD ### ############ init-prod: terraform init -reconfigure -backend-config="key=prod/atlas-knowledge-base-prod.tfstate" plan-prod: terraform plan -var-file=envs/prod/prod.tfvars apply-prod: terraform apply -var-file=envs/prod/prod.tfvars #destroy-prod: # terraform destroy -var-file=envs/prod/prod.tfvars
Які файли будуть далі?
У нас тут ще буде AWS Bedrock, якому треба буде налаштувати доступ – аде це зробимо через його IAM Role, і про Bedrock тут писати не буду – бо і тема окрема, і в Terraform поки що нема підтримки OPENSEARCH_MANAGED, тому ми зробили його руками, а потім виконаємо terraform import.
Індекси, юзерів для нашого Backend API та Bedrock IAM Role mappings будемо робити в internal database самого OpenSearch через Terraform OpenSearch Provider аби не морочитись з доступами до дашборди.
Планування проекту
Кластер можемо зробити просто з ресурсу aws_opensearch_domain.
А можна взяти готові модулі, наприклад opensearch від @Anton Babenko.
Давайте візьмемо модуль Антона, бо я багато де його модулі використовую, в принципі все працює чудово.
Створення кластера
Приклади – terraform-aws-opensearch/tree/master/examples.
До variables.tf додаємо змінну з параметрами кластеру:
...
variable "cluser_options" {
description = "A map of options to configure the OpenSearch cluster"
type = object({
instance_type = string
instance_count = number
volume_size = number
volume_type = string
engine_version = string
auto_software_update_enabled = bool
})
}
І значення в prod.tfvars:
...
cluser_options = {
instance_type = "t3.small.search"
instance_count = 3
volume_size = 50
volume_type = "gp3"
engine_version = "OpenSearch_2.19"
auto_software_update_enabled = true
}
Інстанси t3.small.search – самі мінімальні, нам цього поки що вистачить, хоча для t3 є обмеження – наприклад не підтримується Auto-tune.
Ну і взагалі t3 не для Production use case. Див. також Operational best practices for Amazon OpenSearch Service, Current generation instance types і Amazon OpenSearch Service quotas.
Версію тут я задавав 2.9, але буквально на днях додали 3.1 – див. Supported versions of Elasticsearch and OpenSearch.
Беремо три ноди, аби кластер міг вибрати cluster manager node, якщо одна нода впаде, див. Dedicated master node distribution, Learning OpenSearch from scratch, part 2: Digging deeper і Enhance stability with dedicated cluster manager nodes using Amazon OpenSearch Service.
Зміст locals.tf:
locals {
# 'atlas-kb-prod'
env_name = "${var.project_name}-${var.environment}"
}
Більша частина locals буде саме тут, але деякі, які зовсім вже “локальні” до якогось коду – будуть у файлах з кодом ресурсів.
Додаємо файл opensearcth_users.tf – поки тут тільки рутовий юзер, пароль зберігаємо в AWS Parameter Store (замість AWS Secrets Manager – “так історично склалося”):
############
### ROOT ###
############
# generate root password
# waiting for write-only: https://github.com/hashicorp/terraform-provider-aws/pull/43621
# then will update it with the ephemeral type
resource "random_password" "os_master_password" {
length = 16
special = true
}
# store the root password in AWS Parameter Store
resource "aws_ssm_parameter" "os_master_password" {
name = "/${var.environment}/${local.env_name}-root-password"
description = "OpenSearch cluster master password"
type = "SecureString"
value = random_password.os_master_password.result
overwrite = true
tier = "Standard"
lifecycle {
ignore_changes = [value] # to prevent diff every time password is regenerated
}
}
data "aws_ssm_parameter" "os_master_password" {
name = "/${var.environment}/${local.env_name}-root-password"
with_decryption = true
depends_on = [aws_ssm_parameter.os_master_password]
}
Пишемо файл opensearch_cluster.tf.
Я тут залишив конфіг для VPC, і на майбутнє, і просто для прикладу, хоча перенести вже створений кластер у VPC не можна буде – доведеться створювати новий, див. Limitations в документації Launching your Amazon OpenSearch Service domains within a VPC:
module "opensearch" {
source = "terraform-aws-modules/opensearch/aws"
version = "~> 2.0.0"
# enable Fine-grained access control
# by using the internal user database, we'll simply access to the Dashboards
# for backend API Kubernetes Pods, will use Kubernetes Secrets with username:password from AWS Parameter Store
advanced_security_options = {
enabled = true
anonymous_auth_enabled = false
internal_user_database_enabled = true
master_user_options = {
master_user_name = "os_root"
master_user_password = data.aws_ssm_parameter.os_master_password.value
}
}
# can't be used with t3 instances
auto_tune_options = {
desired_state = "DISABLED"
}
# have three data nodes - t3.small.search nodes in two AZs
# will use 3 indexes - dev/stage/prod with 1 shard and 1 replica each
cluster_config = {
instance_count = var.cluser_options.instance_count
dedicated_master_enabled = false
instance_type = var.cluser_options.instance_type
# put both data-nodes in different AZs
zone_awareness_config = {
availability_zone_count = 2
}
zone_awareness_enabled = true
}
# the cluster's name
# 'atlas-kb-prod'
domain_name = "${local.env_name}-cluster"
# 50 GiB for each Data Node
ebs_options = {
ebs_enabled = true
volume_type = var.cluser_options.volume_type
volume_size = var.cluser_options.volume_size
}
encrypt_at_rest = {
enabled = true
}
# latest for today:
# https://docs.aws.amazon.com/opensearch-service/latest/developerguide/what-is.html#choosing-version
engine_version = var.cluser_options.engine_version
# enable CloudWatch logs for Index and Search slow logs
# TODO: collect to VictoriaLogs or Loki, and create metrics and alerts
log_publishing_options = [
{ log_type = "INDEX_SLOW_LOGS" },
{ log_type = "SEARCH_SLOW_LOGS" },
]
ip_address_type = "ipv4"
node_to_node_encryption = {
enabled = true
}
# allow minor version updates automatically
# will be performed during off-peak windows
software_update_options = {
auto_software_update_enabled = var.cluser_options.auto_software_update_enabled
}
# DO NOT use 'atlas-vpc-ops' VPC and its private subnets
# > "The OpenSearch Managed Cluster you provided is not supported because it is VPC protected. Your cluster must be behind a public network."
# vpc_options = {
# subnet_ids = data.aws_subnets.private.ids
# }
# # VPC endpoint to access from Kubernetes Pods
# vpc_endpoints = {
# one = {
# subnet_ids = data.aws_subnets.private.ids
# }
# }
# Security Group rules to allow access from the VPC only
# security_group_rules = {
# ingress_443 = {
# type = "ingress"
# description = "HTTPS access from VPC"
# from_port = 443
# to_port = 443
# ip_protocol = "tcp"
# cidr_ipv4 = data.aws_vpc.ops_vpc.cidr_block
# }
# }
# Access policy
# necessary to allow access for AWS user to the Dashboards
access_policy_statements = [
{
effect = "Allow"
principals = [{
type = "*"
identifiers = ["*"]
}]
actions = ["es:*"]
}
]
# 'atlas-kb-ops-os-cluster'
tags = {
Name = "${var.project_name}-${var.environment}-os-cluster"
}
}
В принципі, тут все в коментах описано, але кратко:
- включаємо fine-grained access control і локальну базу юзерів
- три дата-ноди, кожна з 50 гіг дисків, в різних Availability Zones
- включаємо логи в CloudWatch
кластер робимо в приватних сабнетах- в Domain Access Policy дозволяємо доступ для всіх
- ну – поки так… Security Groups ми використати не можемо, бо не в VPC, а створити IP-Based policy – як? ми ж не знаємо CIDR Bedrock
- в принципі, тут в
principals.identifiersможна додати ліміт на наших IAM Users + Bedrock AIM Role, бо вона буде одна
Запускаємо створення кластера і йдемо пити чай.
Налаштування Custom endpoint
Після створення кластеру перевіряємо доступ до дашборди, якщо все ОК – то додаємо Custom endpoint.
Note: з Custom endpoint свої приколи: в Terraform OpenSearch Provider треба використовувати саме Custom endpoint URL, але в AWS Bedrock Knowledge Base – дефолтний URL кластеру
Для цього нам треба зробити сертифікат в AWS Certificate Manager і додати новий запис в Route53.
Я тут очікував можливу проблему куриця і яйця, бо налаштування Custom Endpoint залежать від AWS ACM і запису в AWS Route53, а запис в AWS Route53 буде залежати від кластеру – бо використовує його ендпоінт.
Але ні, якщо робити новий кластер з налаштуваннями, які описав нижче – все нормально створюється: спочатку сертифікат в AWS ACM, потім кластер з Custom Endpoint, потім запис в Route53 з CNAME на cluster default URL.
Додаємо нову local – os_custom_domain_name:
locals {
# 'atlas-kb-prod'
env_name = "${var.project_name}-${var.environment}"
# 'opensearch.prod.example.co'
os_custom_domain_name = "opensearch.${var.dns_zone}"
}
Додаємо отримання даних про Route53 зону до data.tf:
...
data "aws_route53_zone" "zone" {
name = var.dns_zone
}
Додаємо створення сертифіката і запис у Route53 до opensearch_cluster.tf:
# TLS for the Custom Domain
module "prod_opensearch_acm" {
source = "terraform-aws-modules/acm/aws"
version = "~> 6.0"
# 'opensearch.example.co'
domain_name = local.os_custom_domain_name
zone_id = data.aws_route53_zone.zone.zone_id
validation_method = "DNS"
wait_for_validation = true
tags = {
Name = local.os_custom_domain_name
}
}
resource "aws_route53_record" "opensearch_domain_endpoint" {
zone_id = data.aws_route53_zone.zone.zone_id
name = local.os_custom_domain_name
type = "CNAME"
ttl = 300
records = [module.opensearch.domain_endpoint]
}
...
І в module "opensearch" додаємо налаштування custom ендпоінту:
...
domain_endpoint_options = {
custom_endpoint_certificate_arn = module.prod_opensearch_acm.acm_certificate_arn
custom_endpoint_enabled = true
custom_endpoint = local.os_custom_domain_name
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
...
Виконуємо terrform init та terrform apply, перевіряємо налаштування:
І перевіряємо доступ до дашборд.
Terraform Outputs
Додамо трохи аутуптів.
Поки просто для себе, потім, можливо, будемо використовувати в імпортах інших проектів, див. Terraform: terraform_remote_state – отримання outputs інших state-файлів:
output "vpc_id" {
value = var.vpc_id
}
output "cluster_arn" {
value = module.opensearch.domain_arn
}
output "opensearch_domain_endpoint_cluster" {
value = "https://${module.opensearch.domain_endpoint}"
}
output "opensearch_domain_endpoint_custom" {
value = "https://${local.os_custom_domain_name}"
}
output "opensearch_root_username" {
value = "os_root"
}
output "opensearch_root_user_password_secret_name" {
value = "/${var.environment}/${local.env_name}-root-password"
}
Створення OpenSearch Users
Власне, що нам залишилось – це користувачі і індекси.
Юзерів у нас буде два типи:
- звичайні юзери з OpenSearch internal database – для нашого Backend API в Kubernetes (насправді, потім ми все ж перейшли на IAM Roles, які мапляться в поди Backend через EKS Pod Identities)
- і юзери (IAM Role) для Bedrock – там буде три Knowledge Bases, кожна зі своєю IAM Role, для якої треба буде додати OpenSearch Role і зробити mapping на IAM-ролі
Почнемо зі звичайних юзерів.
Додаємо провайдера, в мене це у файлі versions.tf:
terraform {
required_version = "~> 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
opensearch = {
source = "opensearch-project/opensearch"
version = "~> 2.3"
}
}
}
В файлі providers.tf описуємо доступ до кластеру:
...
provider "opensearch" {
url = "https://${local.os_custom_domain_name}"
username = "os_root"
password = data.aws_ssm_parameter.os_master_password.value
healthcheck = false
}
Error: elastic: Error 403 (Forbidden)
Тут важливий момент з url в конфігурації провайдеру, писав про це вище, тепер – як воно виглядає.
Спершу в provider.url задав як outputs модуля, тобто module.opensearch.domain_endpoint.
І через це ловив 403, коли намагався створити юзерів:
...
opensearch_user.os_kraken_dev_user: Creating...
opensearch_role.os_kraken_dev_role: Creating...
╷
│ Error: elastic: Error 403 (Forbidden)
│
│ with opensearch_user.os_kraken_dev_user,
│ on opensearch_users.tf line 23, in resource "opensearch_user" "os_kraken_dev_user":
│ 23: resource "opensearch_user" "os_kraken_dev_user" {
│
╵
╷
│ Error: elastic: Error 403 (Forbidden)
│
│ with opensearch_role.os_kraken_dev_role,
│ on opensearch_users.tf line 30, in resource "opensearch_role" "os_kraken_dev_role":
│ 30: resource "opensearch_role" "os_kraken_dev_role" {
Власне, задаємо URL саме у вигляді FQDN, який робили для Custom Endpoint, щось типу "url = https://opensearch.exmaple.com" – і з ним все працює.
Створення Internal юзерів
Тепер самі юзери.
Їх буде три – dev, staging, prod, кожен з доступом до відповідного індексу.
Тут використаємо opensearch_user.
Якщо кластер всеж створений в VPC – то потрібен підключений VPN, аби провайдер зміг підключитись до кластеру.
До variables.tf додаємо list() зі списком оточень:
...
variable "app_environments" {
type = list(string)
description = "The Application's environments, to be used to created Dev/Staging/Prod DynamoDB tables, etc"
}
І значення в prod.tfvars:
... app_environments = [ "dev", "staging", "prod" ]
Internal database users
Спершу я планував просто використовувати локальних юзерів, і в цей пост записав такий варіант – нехай буде. Далі покажу, як все ж зробили потім – з IAM Users та IAM Roles.
У файлі opensearch_users.tf додаємо в циклах три паролі, трьох юзерів, і три ролі, на які мапимо юзерів – кожна роль з доступом до власного індексу:
...
##############
### KRAKEN ###
##############
resource "random_password" "os_kraken_password" {
for_each = toset(var.app_environments)
length = 16
special = true
}
# store the root password in AWS Parameter Store
resource "aws_ssm_parameter" "os_kraken_password" {
for_each = toset(var.app_environments)
name = "/${var.environment}/${local.env_name}-kraken-${each.key}-password"
description = "OpenSearch cluster Backend Dev password"
type = "SecureString"
value = random_password.os_kraken_password[each.key].result
overwrite = true
tier = "Standard"
lifecycle {
ignore_changes = [value] # to prevent diff every time password is regenerated
}
}
# Create a user
resource "opensearch_user" "os_kraken_user" {
for_each = toset(var.app_environments)
username = "os_kraken_${each.key}"
password = random_password.os_kraken_password[each.key].result
description = "Backend EKS ${each.key} user"
depends_on = [module.opensearch]
}
# And a full user, role and role mapping example:
resource "opensearch_role" "os_kraken_role" {
for_each = toset(var.app_environments)
role_name = "os_kraken_${each.key}_role"
description = "Backend EKS ${each.key} role"
cluster_permissions = [
"indices:data/read/msearch",
"indices:data/write/bulk*",
"indices:data/read/mget*"
]
index_permissions {
index_patterns = ["kraken-kb-index-${each.key}"]
allowed_actions = ["*"]
}
depends_on = [module.opensearch]
}
В cluster_permissions додаємо дозволи, які потрібні і для index level, і для cluster level, бо Bedrock без них не працював, див. Cluster wide index permissions.
Деплоїмо, перевіряємо в Dashboards:
Додавання IAM Users
Тут ідея така сама, просто замість звичайних юзерів з логіном:паролем для аутентифікації використовується IAM та його Users && Roles.
Про роль для Bedrock далі, а зараз додамо мапінг юзерів.
Що нам треба – це взяти список наших Backend team юзерів, дати їм IAM Policy з доступом до OpenSearch, а потім в OpnSearch internal users database додати мапінг на локальну роль.
Локальну роль поки можна взяти all_access, хоча краще потім все ж написати власну. Див. Predefined roles та About the master user.
Додаємо нову змінну в variables.tf:
...
variable "backend_team_users_arns" {
type = list(string)
}
Її значення в prod.tfvars:
... backend_team_users_arns = [ "arn:aws:iam::492***148:user/arseny", "arn:aws:iam::492***148:user/misha", "arn:aws:iam::492***148:user/oleksii", "arn:aws:iam::492***148:user/vladimir", "os_root" ]
Тут довелося костиляти з юзером os_root, бо інакше його випилює з ролі.
Тому таки краще зробити нормальні ролі – але для MVP міжна і так.
І додаємо мапінг цих IAM Users до ролі all_access:
...
####################
### BACKEND TEAM ###
####################
resource "opensearch_roles_mapping" "all_access_mapping" {
role_name = "all_access"
users = var.backend_team_users_arns
}
Деплоїмо, перевіряємо роль all_access:
Note: ChatGPT вперто казав додавати IAM Users в Backend Roles, але ні, і це явно вказано в документації – додавати треба в Users, див. Additional master users.
І всім IAM Users треба додати IAM-політику з доступом.
Знов-таки для MVP можна просто взяти голову policy AmazonOpenSearchServiceFullAccess, яка підключена до IAM Group:
Створення AWS Bedrock IAM Roles та OpenSearch Role mappings
Bedrock у нас вже є, треба просто створити нові IAM Roles і замапити їх до OpenSeach Roles.
Додаємо файл iam.tf – описуємо IAM Role та IAM Policy (Identity-based Policy для доступу до OpenSearch), тут також в циклі по кожному з var.app_environmetns:
#####################################
### MAIN ROLE FOR KNOWLEDGE BASE ###
#####################################
# grants permissions for AWS Bedrock to interact with other AWS services
resource "aws_iam_role" "knowledge_base_role" {
for_each = toset(var.app_environments)
name = "${var.project_name}-role-${each.key}-managed"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "bedrock.amazonaws.com"
}
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
}
ArnLike = {
# restricts the role to be assumed only by Bedrock knowledge base in the specified region
"aws:SourceArn" = "arn:aws:bedrock:${var.aws_region}:${data.aws_caller_identity.current.account_id}:knowledge-base/*"
}
}
}
]
})
}
# IAM policy for Knowledge Base to access OpenSearch Managed
resource "aws_iam_policy" "knowledge_base_opensearch_policy" {
for_each = toset(var.app_environments)
name = "${var.project_name}-kb-opensearch-policy-${each.key}-managed"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"es:*",
]
Resource = [
module.opensearch.domain_arn,
"${module.opensearch.domain_arn}/*"
]
}
]
})
}
resource "aws_iam_role_policy_attachment" "knowledge_base_opensearch" {
for_each = toset(var.app_environments)
role = aws_iam_role.knowledge_base_role[each.key].name
policy_arn = aws_iam_policy.knowledge_base_opensearch_policy[each.key].arn
}
Далі в opensearch_users.tf створимо:
opensearch_role: зcluster_permissionsтаindex_permissionsна кожен індексlocalsз усіма IAM Roles, які створили вище- і
opensearch_roles_mappingдля кожноїopensearch_role.os_bedrock_roles, які черезbackend_rolesдодаємо до кожноїopensearch_role
Виглядає якось так:
...
#################
#### BEDROCK ####
#################
resource "opensearch_role" "os_bedrock_roles" {
for_each = toset(var.app_environments)
role_name = "os_bedrock_${each.key}_role"
description = "Backend Bedrock KB ${each.key} role"
cluster_permissions = [
"indices:data/read/msearch",
"indices:data/write/bulk*",
"indices:data/read/mget*"
]
index_permissions {
index_patterns = ["kraken-kb-index-${each.key}"]
allowed_actions = ["*"]
}
depends_on = [module.opensearch]
}
# 'aws_iam_role' is defined in iam.tf
locals {
knowledge_base_role_arns = {
for env, role in aws_iam_role.knowledge_base_role :
env => role.arn
}
}
resource "opensearch_roles_mapping" "os_bedrock_role_mappings" {
for_each = toset(var.app_environments)
role_name = opensearch_role.os_bedrock_roles[each.key].role_name
backend_roles = [
local.knowledge_base_role_arns[each.key]
]
depends_on = [module.opensearch]
}
Власне, саме тут зіткнулись з помилками доступу Bedrock, через які довелось додавати cluster_permissions:
The knowledge base storage configuration provided is invalid… Request failed: [security_exception] no permissions for [indices:data/read/msearch] and User [name=arn:aws:iam::492***148:role/kraken-kb-role-dev, backend_roles=[arn:aws:iam::492***148:role/kraken-kb-role-dev], requestedTenant=null]
Деплоїмо, перевіряємо:
Створення OpenSearch індексів
Провайдер вже є, ресурс беремо opensearch_index.
В locals записуємо шаблон індексу – я його просто взяв у девелоперів зі старого конфігу:
locals {
# 'atlas-kb-prod'
env_name = "${var.project_name}-${var.environment}"
# 'opensearch.prod.example.co'
os_custom_domain_name = "opensearch.${var.dns_zone}"
# index mappings
os_index_mappings = <<-EOF
{
"dynamic_templates": [
{
"strings": {
"match_mapping_type": "string",
"mapping": {
"fields": {
"keyword": {
"ignore_above": 8192,
"type": "keyword"
}
},
"type": "text"
}
}
}
],
"properties": {
"bedrock-knowledge-base-default-vector": {
"type": "knn_vector",
"dimension": 1024,
"method": {
"name": "hnsw",
"engine": "faiss",
"parameters": {
"m": 16,
"ef_construction": 512
},
"space_type": "l2"
}
},
"AMAZON_BEDROCK_METADATA": {
"type": "text",
"index": false
},
"AMAZON_BEDROCK_TEXT_CHUNK": {
"type": "text",
"index": true
}
}
}
EOF
}
Створюємо файл opensearch_indexes.tf. І додаємо сам індекси – тут я все ж вирішив без циклу, прямо створити окремі Dev/Staging/Prod:
# Dev Index
resource "opensearch_index" "kb_vector_index_dev" {
name = "kraken-kb-index-dev"
# enable approximate nearest neighbor search by setting index_knn to true
index_knn = true
index_knn_algo_param_ef_search = "512"
number_of_shards = "1"
number_of_replicas = "1"
mappings = local.os_index_mappings
# When new documents are ingested into the Knowledge Base,
# OpenSearch automatically creates field mappings for new metadata fields under
# AMAZON_BEDROCK_METADATA. Since these fields are created outside of TF resource definitions,
# TF detects them as configuration drift and attempts to recreate the index to match its
# known state.
#
# This lifecycle rule prevents unnecessary index recreation by ignoring mapping changes
# that occur after initial deployment.
lifecycle {
ignore_changes = [mappings]
}
}
...
Деплоїмо і перевіряємо:
Власне, на цьому і все.
Bedrock вже підключили, все працює.
Але трохи погемороїтись довелось.
І впевнений, що не останній раз 🙂










