Agents on Call, Μέρος 2. Το θεμέλιο: Terraform πριν τα tokens
Πριν περάσει έστω ένα Bedrock token: ο λογαριασμός ops-tooling, οι δύο ρόλοι IAM, η πρόσβαση σε μοντέλα, και η απόφαση inference profile, όλα σε Terraform.

Πριν οποιοσδήποτε από τους τέσσερις agents του Μέρους 1 μπορέσει να κοιτάξει ένα log, να τιμολογήσει ένα workload, ή να προτείνει μια διόρθωση, αυτή η πλατφόρμα χρειάζεται ένα όριο λογαριασμού και δύο ρόλους IAM που κάνουν το «read-only by default» μια ιδιότητα επιβεβλημένη από την AWS, όχι μια υπόσχεση μέσα σε ένα system prompt. Αυτό το όριο, το αίτημα πρόσβασης σε μοντέλα που πρέπει να γίνει μέρες πριν σχεδιάσει κανείς να κάνει κάποιο demo, και η απόφαση ανάμεσα σε on-demand, provisioned throughput, και cross-region inference profiles είναι αυτά που χτίζονται σε αυτό το μέρος, εξ ολοκλήρου σε Terraform, πριν υπάρξει έστω μία γραμμή κώδικα agent.
Το Μέρος 1 έστησε τη σκηνή: μια μεσαίου μεγέθους SaaS εταιρεία με 30 λογαριασμούς AWS και 40 ειδοποιήσεις την εβδομάδα, που επιλέγει να χτίσει μια πλατφόρμα multi-agent ops πάνω στο Bedrock AgentCore και το Strands αντί να αγοράσει μία, με το read-only επιβεβλημένο από το IAM ως τον μη διαπραγματεύσιμο περιορισμό σχεδιασμού. Αυτό το μέρος είναι εκεί όπου αυτός ο περιορισμός σταματά να είναι διάγραμμα και γίνεται HCL. Ο συνοδευτικός κώδικας βρίσκεται στο github.com/flightlesstux/agents-on-call, στο terraform/00-foundation/, και κάθε απόσπασμα παρακάτω είναι αντιγραμμένο από αυτόν τον φάκελο, όχι απλοποιημένο για το άρθρο.
Γιατί ένας αποκλειστικός λογαριασμός ops-tooling
Η διάταξη λογαριασμών από το Μέρος 1 τοποθετεί ολόκληρη την πλατφόρμα agents σε έναν λογαριασμό, ξεχωριστό από τους ~30 λογαριασμούς workload πάνω στους οποίους λειτουργεί:
| Λογαριασμός | Ρόλος |
|---|---|
| management | Org root, SCPs, χωρίς workloads |
| security | Org CloudTrail, GuardDuty και Security Hub delegated admin |
| ops-tooling | Ολόκληρη η πλατφόρμα agents: Runtime, Gateway, Memory, η state machine έγκρισης του Step Functions |
| workload × ~30 | Spokes. Εκθέτουν ακριβώς δύο ρόλους στο ops-tooling |
Ένας μοναδικός αποκλειστικός λογαριασμός, αντί να αναπτύσσεται η υποδομή agents μέσα σε κάθε λογαριασμό workload, αγοράζει τρία πράγματα που ένας κοινός λογαριασμός δεν μπορεί. Πρώτον, ακτίνα έκρηξης: μια λανθασμένα ρυθμισμένη Lambda ή ένα διαρρεύσαν credential στο ops-tooling δεν μπορεί να αγγίξει απευθείας τίποτα σε ένα spoke, επειδή η πρόσβαση σε ένα spoke απαιτεί πάντα ρητή κλήση sts:AssumeRole πέρα από ένα όριο λογαριασμού, όχι μια σιωπηρή άδεια εντός του ίδιου λογαριασμού. Δεύτερον, ένα σημείο ελέγχου: το CloudTrail στον λογαριασμό security βλέπει κάθε κλήση cross-account assume-role από έναν και μόνο λογαριασμό πηγής, αντί να συσχετίζει δραστηριότητα agents σκορπισμένη σε 30 trails. Τρίτον, ανεξάρτητη ακτίνα έκρηξης για την ίδια την πλατφόρμα: αν το ops-tooling έχει ένα incident, υποβαθμίζεται στο status quo παντού αλλού, ακριβώς η ιδιότητα προσθετικό-όχι-κρίσιμο-μονοπάτι που το Μέρος 1 έθεσε ως απαίτηση.
Οι δύο ρόλοι που εκθέτει κάθε spoke
Κάθε λογαριασμός workload εκθέτει ακριβώς δύο ρόλους στο ops-tooling, και τίποτα άλλο. Και οι δύο ζουν σε ένα επαναχρησιμοποιήσιμο Terraform module, modules/spoke-roles/, φτιαγμένο να εφαρμόζεται μία φορά ανά λογαριασμό spoke: ένα provider alias ανά λογαριασμό δουλεύει για μια χούφτα spokes, και σε περίπου 30 λογαριασμούς αυτό τρέχει αντ' αυτού από ένα pipeline ανά λογαριασμό, για παράδειγμα ένα Terraform Cloud workspace ανά λογαριασμό ή μια προσαρμογή Landing Zone Account Factory. Το root module σε αυτό το repo το στιγμιοτυπιάζει μία φορά πάνω σε έναν μοναδικό provider ώστε το παράδειγμα να παραμένει εκτελέσιμο με terraform validate.
ops-readonly: χτισμένος από managed policies, οριοθετημένος από μια ρητή απαγόρευση
Τα εργαλεία ανάγνωσης (cloudwatch-read, logs-read, cost-read) αναλαμβάνουν όλα αυτόν τον έναν ρόλο. Η trust policy του ονομάζει τα ακριβή Lambda execution role ARNs που επιτρέπεται να τον αναλάβουν, συν ένα κοινό external ID ως δεύτερο παράγοντα ενάντια στο πρόβλημα confused-deputy:
data "aws_iam_policy_document" "ops_readonly_trust" {
statement {
sid = "AssumeFromOpsToolingReadTools"
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "AWS"
identifiers = var.ops_readonly_assumer_role_arns
}
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = [var.external_id]
}
}
}
resource "aws_iam_role" "ops_readonly" {
name = "ops-readonly"
assume_role_policy = data.aws_iam_policy_document.ops_readonly_trust.json
max_session_duration = 3600
}Τα δικαιώματα προέρχονται από δύο managed policies της AWS: το CloudWatchReadOnlyAccess καλύπτει τόσο τα CloudWatch metrics όσο και τις αναγνώσεις CloudWatch Logs σε ένα attachment, γι' αυτό και τα cloudwatch-read και logs-read μπορούν να μοιράζονται έναν ενιαίο ρόλο IAM αντί να χρειάζονται δύο, και το AWSBillingReadOnlyAccess καλύπτει τις κλήσεις Cost Explorer που χρειάζεται το cost-read. Πάνω από τα δύο αυτά, μια ρητή δήλωση deny μπλοκάρει μια σταθερή λίστα ενεργειών mutation (iam:*, ssm:StartAutomationExecution, ec2:TerminateInstances, s3:PutObject*, και περίπου μια ντουζίνα ακόμα) ανεξάρτητα από το τι επιτρέπουν οι συνδεδεμένες managed policies. Αυτό το τελευταίο κομμάτι δεν είναι διακόσμηση. Οι managed policies της AWS αποκτούν νέες ενέργειες με τον καιρό καθώς οι υπηρεσίες προσθέτουν λειτουργίες, και μια ρητή απαγόρευση είναι το μόνο πράγμα που μια αξιολόγηση IAM δεν μπορεί να παρακάμψει με ένα allow, ανεξάρτητα από το ποια policy, ή ποια μελλοντική έκδοση μιας policy, το παρήγαγε.
ops-mutate: ένας caller, μία allowlist, καμία managed policy
Η trust policy είναι ακόμα πιο στενή: ακριβώς ένας principal, η Lambda post-approval executor που τρέχει μόνο αφότου ένας άνθρωπος πατήσει approve στο Slack.
data "aws_iam_policy_document" "ops_mutate_trust" {
statement {
sid = "AssumeFromPostApprovalExecutorOnly"
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "AWS"
identifiers = [var.ops_mutate_assumer_role_arn]
}
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = [var.external_id]
}
}
}Η permission policy είναι το πιο ενδιαφέρον μισό. Το ssm:StartAutomationExecution υποστηρίζει περιορισμό σε επίπεδο πόρου σε συγκεκριμένα SSM Automation document ARNs, οπότε η allowlist των runbooks που μπορεί να τρέξει αυτός ο ρόλος επιβάλλεται απευθείας από το IAM, όχι από λογική εφαρμογής που θα μπορούσε να έχει bug:
data "aws_iam_policy_document" "ops_mutate_permissions" {
statement {
sid = "StartOnlyAllowlistedRunbooks"
effect = "Allow"
actions = ["ssm:StartAutomationExecution"]
resources = var.allowed_ssm_automation_document_arns
}
statement {
sid = "InspectAndStopOwnExecutions"
effect = "Allow"
actions = [
"ssm:GetAutomationExecution",
"ssm:DescribeAutomationExecutions",
"ssm:StopAutomationExecution",
]
resources = ["*"]
}
}Η δεύτερη δήλωση είναι σε Resource = "*" για συγκεκριμένο λόγο: το GetAutomationExecution και οι άλλες κλήσεις επιθεώρησης λειτουργούν πάνω σε ένα execution ID που δεν υπάρχει μέχρι το StartAutomationExecution να έχει ήδη επιστρέψει ένα, οπότε δεν υπάρχει τίποτα στο οποίο να τα περιορίσουμε εκ των προτέρων. Το ουσιαστικό όριο σε αυτόν τον ρόλο είναι ποια έγγραφα μπορεί να ξεκινήσει, όχι ποιες από τις δικές του ήδη τρέχουσες εκτελέσεις μπορεί να ελέγξει. Και σε αντίθεση με το ops-readonly, αυτός ο ρόλος δεν έχει καμία σύνδεση με managed policy της AWS. Κάθε δικαίωμα που κατέχει απαριθμείται σε εκείνο το ένα έγγραφο policy, οπότε μια αλλαγή από την πλευρά της AWS σε μια managed policy δεν μπορεί ποτέ να διευρύνει σιωπηλά τι μπορεί να κάνει.
Η πρόσβαση σε μοντέλα Bedrock είναι πρόβλημα ημερών στο console
Δεν υπάρχει Terraform resource για την παραχώρηση πρόσβασης σε foundation μοντέλα του Bedrock, και αυτό το κενό αξίζει να αναφερθεί πριν κοστίσει ένα session debugging. Η ενεργοποίηση ενός μοντέλου για έναν λογαριασμό είναι ένα χειρωνακτικό βήμα, είτε η σελίδα «Model access» του Bedrock console είτε η αντίστοιχη μεμονωμένη κλήση API, που ενεργοποιεί μια ροή αποδοχής EULA την οποία η AWS επεξεργάζεται ασύγχρονα. Δεν είναι στιγμιαίο, και δεν είναι idempotent υποδομή που μπορεί να εκφράσει ένα plan και apply, οπότε δεν μπορεί να ζει στο ίδιο repo με τους ρόλους IAM παραπάνω.
Το πρακτικό αποτέλεσμα: ζήτησε πρόσβαση για κάθε μοντέλο που θα καλέσει αυτή η πλατφόρμα, σε κάθε περιοχή στην οποία μπορεί να δρομολογήσει ένα cross-region inference profile, πριν το πρώτο terraform apply, όχι μετά. Ένα λείπον entitlement σε μία περιοχή ενός multi-region profile αποτυγχάνει μόνο στις κλήσεις που δρομολογούνται εκεί, κάτι που κάνει την αποτυχία να μοιάζει διαλείπουσα, έναν ασταθή agent, ένα παροδικό throttle, αντί για αυτό που πραγματικά είναι: μία περιοχή από τις πολλές που ακόμα περιμένει ένα κλικ στο console που δεν έκανε κανείς ακόμα. Βάλε το αυτό στο πλάνο ως εργασία της πρώτης μέρας, όχι ως εργασία κατά τη διάρκεια του testing, γιατί η ίδια η ροή έγκρισης είναι εκτός ελέγχου του Terraform, και αυτού του project.
On-demand, provisioned, ή cross-region: ο κανόνας απόφασης είναι «on-demand μέχρι να μετρηθεί»
Υπάρχουν τρεις τρόποι να καλέσεις ένα μοντέλο Bedrock, και το να διαλέξεις ανάμεσά τους πριν υπάρξει καθόλου κίνηση για να μετρηθεί είναι μια εικασία ντυμένη σαν απόφαση αρχιτεκτονικής.
| Τρόπος | Πώς χρεώνεται | Τι προσφέρει |
|---|---|---|
| On-demand | Ανά input/output token, τυπικός δημοσιευμένος ρυθμός | Καμία δέσμευση, κλιμακώνεται στο μηδέν, η μόνη λογική προεπιλογή πριν υπάρξει baseline κίνησης |
| Provisioned Throughput | Ωριαία, ανά Model Units· ο ρυθμός εξαρτάται από το μοντέλο, τον αριθμό MU, και τη διάρκεια δέσμευσης (χωρίς δέσμευση, 1 μήνας, 6 μήνες, μεγαλύτερες δεσμεύσεις κοστίζουν λιγότερο ανά ώρα) | Εγγυημένο, αποκλειστικό throughput· πληρώνεις για χωρητικότητα είτε χρησιμοποιείται είτε όχι |
| Cross-region inference profile | Ίδιος ρυθμός ανά token με την περιοχή πηγής, καμία επιβάρυνση δρομολόγησης | Αυτόματη δρομολόγηση σε όλες τις περιοχές μιας γεωγραφίας για περιθώριο burst και αξιοπιστία, μόνο on-demand |
Τα cross-region inference profiles είναι η εύκολη επιλογή: δεν κοστίζουν τίποτα παραπάνω από το on-demand και προσθέτουν χωρητικότητα burst συν ένα μονοπάτι failover αν μια περιοχή κάνει throttle, οπότε αυτή η πλατφόρμα τα χρησιμοποιεί παντού από την πρώτη μέρα. Το Provisioned Throughput είναι η πιο δύσκολη επιλογή, και ο κανόνας που χρησιμοποιεί αυτό το project είναι on-demand μέχρι να μετρηθεί: ένα Model Unit αγοράζει ένα σταθερό ανώτατο όριο input και output tokens ανά λεπτό που χρεώνεται ωριαία είτε η πλατφόρμα στείλει έστω ένα request σε μια δεδομένη ώρα είτε όχι, οπότε το να δεσμευτείς σε αυτό πριν γνωρίζεις την πραγματική κατανομή ρυθμού request ανάμεσα σε τέσσερις agents είναι το να δεσμευτείς σε έναν αριθμό που κανείς δεν μπορεί ακόμα να υπερασπιστεί. Το Μέρος 7 δουλεύει τα πραγματικά μαθηματικά των tokens και το σημείο στο οποίο ο ωριαίος ρυθμός του provisioned throughput ξεπερνά τον ρυθμό ανά token του on-demand· μέχρι να υπάρξουν αυτά τα μαθηματικά, το να μαντεύεις ένα επίπεδο δέσμευσης απλώς μετακινεί τον κίνδυνο από «το μοντέλο είναι αργό» σε «ο λογαριασμός είναι λάθος».
Αυτό που δημιουργεί στην πραγματικότητα το Terraform εδώ δεν είναι το ίδιο το cross-region profile, αυτό το κομμάτι είναι AWS-managed και υπάρχει τη στιγμή που μια περιοχή το υποστηρίζει, αλλά ένα application inference profile ανά agent που τυλίγει το system-defined profile:
resource "aws_bedrock_inference_profile" "agent" {
for_each = var.agents
name = "${var.platform_name}-${each.key}"
description = "Application inference profile for the ${each.key} agent, tagged for per-agent cost allocation."
model_source {
copy_from = each.value.cross_region_profile_arn
}
tags = merge(var.tags, {
Agent = each.key
})
}Το tag Agent είναι όλο το νόημα του να τυλίγεις το system profile αντί να το καλείς απευθείας: χωρίς αυτό, η δαπάνη Bedrock κάθε agent καταλήγει σε μία αδιαφοροποίητη γραμμή, και η απαίτηση κατανομής κόστους ανά agent από την αρχιτεκτονική του Μέρους 1 δεν έχει τίποτα πάνω στο οποίο να στηριχθεί. Με αυτό, το δικό της setup Budgets και Cost Anomaly Detection της πλατφόρμας, που προορίζεται να παρακολουθεί τους agents που παρακολουθούν τα πάντα άλλα, μπορεί πράγματι να αποδώσει τη δαπάνη στον agent που την παρήγαγε.
Το πρώτο terraform apply
Το να τρέξεις terraform -chdir=terraform/00-foundation init και μετά plan είναι ένα μικρό plan σε αριθμό γραμμών, δύο ρόλοι IAM, δύο inline policies, δύο managed policy attachments, και όσα application inference profiles υπάρχουν agents, αλλά είναι το ένα apply σε όλη αυτή τη σειρά που πρέπει να είναι σωστό πριν μπορέσει να εμπιστευτεί κανείς οτιδήποτε παρακάτω. Το committed root module στιγμιοτυπιάζει το spoke_roles μία φορά πάνω στον default provider, οπότε αυτό το παράδειγμα εφαρμόζεται σε έναν μοναδικό λογαριασμό ώστε να παραμένει εκτελέσιμο με terraform validate και plan. Μια πραγματική ανάπτυξη περνά αντ' αυτού ένα provider alias με εμβέλεια spoke σε αυτό το module· χωρίς αυτό, οι δύο ρόλοι καταλήγουν δίπλα δίπλα σε έναν λογαριασμό και το όριο cross-account που υποτίθεται ότι χτίζει αυτό το μέρος δεν υπάρχει. Κάθε επόμενο μέρος υποθέτει ότι τα ops-readonly και ops-mutate υπάρχουν ήδη με ακριβώς αυτό το σχήμα: κάνε λάθος στη λίστα principal της trust policy εδώ, και μια κλήση AssumeRole μιας Lambda εργαλείου θα αποτύχει στο Μέρος 4 με τρόπο που μοιάζει με λανθασμένη ρύθμιση Gateway, όχι με ένα typo του Μέρους 2 τρεις εβδομάδες νωρίτερα.
Μια λεπτομέρεια υλοποίησης που κόστισε μερικά λεπτά την πρώτη φορά: το aws_iam_role.max_session_duration έχει ένα σκληρό κατώτατο όριο 3600 δευτερολέπτων. Το ένστικτο για το ops-mutate, δεδομένου πόσο στενά οριοθετημένη είναι ήδη η ακτίνα έκρηξής του, είναι να ζητήσεις το συντομότερο δυνατό session, αλλά το IAM δεν επιτρέπει ανώτατο όριο σε επίπεδο ρόλου κάτω από μία ώρα. Η πραγματική διάρκεια ζωής credential που ζητά ο post-approval executor κατά την εκτέλεση, μέσω της δικής του κλήσης STS, μπορεί και πρέπει να παραμείνει πολύ πιο σύντομη από αυτό το ανώτατο όριο· η ρύθμιση του ρόλου απλώς οριοθετεί το μέγιστο, δεν ορίζει την προεπιλογή.
Υγιεινή state και backend
Τίποτα σε αυτό το μέρος δεν αγγίζει σκόπιμα ένα remote backend, γιατί το σημείο που θέλει να τονίσει αυτή η ενότητα είναι πιο σημαντικό από το συγκεκριμένο backend που θα επιλεγεί: το state πρέπει να ζει κάπου με locking και encryption πριν ένας δεύτερος άνθρωπος, ή ένα δεύτερο CI run, εφαρμόσει ποτέ πάνω στον ίδιο λογαριασμό, και πρέπει να ζει έξω από οτιδήποτε μπορεί να φτάσει ένας agent ή μια Lambda εργαλείου. Ένα S3 bucket με versioning και έναν πίνακα κλειδώματος DynamoDB, ή το Terraform Cloud, δουλεύουν και τα δύο· αυτό που έχει σημασία είναι ότι τα ops-readonly και ops-mutate δεν έχουν κανένα δικαίωμα να διαβάσουν ή να γράψουν αυτό το state, και ότι οι άνθρωποι που τρέχουν terraform apply πάνω σε αυτό το repo ακολουθούν ένα διαφορετικό, ελεγμένο μονοπάτι από την πλατφόρμα που χτίζει το repo. Το console drift είναι το άλλο μισό αυτής της πειθαρχίας: αλλαγές production που γίνονται κλικάροντας εδώ κι εκεί αποσυγχρονίζουν το αρχείο state από την πραγματικότητα με τρόπους που εμφανίζονται αργότερα ως ένα μπερδεμένο plan, όχι ως ένα άμεσο σφάλμα, κάτι που είναι ακριβώς το επιχείρημα για να αντιμετωπίζεις το IaC ως το μόνο μονοπάτι προς το production, όπως καλύπτεται με περισσότερη λεπτομέρεια στο IaC-First: Why We Never Touch the AWS Console in Production στο ercan.cloud.
Τρόποι αποτυχίας που πρέπει να προσέχεις συγκεκριμένα από αυτό το μέρος
Τρία πράγματα αξίζει να γνωρίζεις πριν εμφανιστούν ως ένα μπερδεμένο σύμπτωμα δύο μέρη από τώρα. Ένα λείπον entitlement μοντέλου σε μία περιοχή ενός cross-region profile αποτυγχάνει μόνο στα requests που δρομολογούνται εκεί, οπότε παρουσιάζεται ως διαλείπουσα αστάθεια, όχι ως καθαρό σφάλμα, μέχρι κάποιος να σκεφτεί να ελέγξει τη σελίδα πρόσβασης στο console αντί για τον κώδικα. Ένα typo στο ops_readonly_assumer_role_arns ή στο ops_mutate_assumer_role_arn κάνει το AssumeRole να αποτύχει με access-denied που δεν δίνει καμία ένδειξη για το ποια πλευρά, trust policy ή caller, είναι λάθος, οπότε κράτα αυτά τα ARNs ρόλων Lambda ως Terraform outputs από όπου δημιουργούνται και αναφέρσου σε αυτά, μην ξαναγράφεις ποτέ ένα ARN με το χέρι. Και μια άδεια λίστα allowed_ssm_automation_document_arns είναι έγκυρο Terraform, ο ρόλος εφαρμόζεται καθαρά, αναλαμβάνεται καθαρά, και μετά δεν μπορεί ποτέ στην πραγματικότητα να ξεκινήσει τίποτα· ένα validation block σε επίπεδο plan πιάνει αυτή τη συγκεκριμένη περίπτωση, αλλά μόνο επειδή κάποιος σκέφτηκε να το προσθέσει, όχι επειδή το Terraform το πιάνει μόνο του.
Διάβασε επίσης
- Μέρος 1, Το σενάριο: Γιατί μια ομάδα Ops προσλαμβάνει agents, για την απογραφή χειρωνακτικής δουλειάς, την απόφαση stack, και τη διάταξη λογαριασμών που χτίζει αυτό το μέρος σε Terraform.
- Μέρος 3, Πρώτος Agent: Incident Triage στο Strands, όπου ο πρώτος agent Strands αναπτύσσεται πάνω στο AgentCore Runtime χρησιμοποιώντας τον ρόλο
ops-readonlyπου χτίστηκε εδώ. - IaC-First: Why We Never Touch the AWS Console in Production στο ercan.cloud, το επιχείρημα state-drift και console-discipline στο οποίο στηρίζεται η ενότητα backend αυτού του μέρους, από την πλευρά της γενικής υποδομής αντί από την πλευρά της πλατφόρμας agents.
Το πλήρες module terraform/00-foundation/, συμπεριλαμβανομένων των κομματιών που κόπηκαν από τα αποσπάσματα παραπάνω, ζει στο συνοδευτικό repository στο github.com/flightlesstux/agents-on-call. Για την πλευρά βάσεων δεδομένων και υποδομής της λειτουργίας πλατφορμών σαν αυτή σε κλίμακα, οι σημειώσεις πεδίου είναι στο ercan.cloud, και ο κόμβος είναι στο ercanermis.com.
Περισσότερα από τον Ercan
Δύο ακόμη ιστότοποι, ίδιος συγγραφέας, διαφορετικό έδαφος.
Cloud, AWS, EKS, Terraform, platform engineering.
Σημειώσεις πεδίου από συστήματα παραγωγής. EKS, IAM, Terraform σε κλίμακα οργανισμού, observability, βελτιστοποίηση κόστους.
Επισκεφθείτε ercan.cloud →Ο κόμβος. Σχετικά, συμβουλευτική, επικοινωνία.
Προσωπικός κόμβος και για τις δύο διαδρομές γραφής. Ποιος είμαι, πώς λειτουργεί η συμβουλευτική, πώς να επικοινωνήσετε.
Επισκεφθείτε ercanermis.com →