Deploying SAP HANA 2.0 on AWS Using Terraform and Ansible

Introduction

This post is a trimmed-down version of the core automation work from my MSc final project, adapted here as a practical walkthrough for the SAP Community. I’ve left out the React UI side of things — Terraform and Ansible are dyed-in-the-wool command line tools, and covering the UI layer in full would make this a very different (and much longer) post.

The goal was a fully automated SAP HANA 2.0 SPS08 deployment on AWS, driven from a React web UI. Terraform provisions the infrastructure, Ansible handles the HANA installation, and a Node.js backend streams the output live to the browser. 

Prerequisites

You’ll need the following installed and configured locally before you start:

Terraform (v1.0+) — terraform.ioAnsible (v2.12+) — pip install ansibleAWS CLI v2aws.amazon.com/cliAWS credentials configured with sufficient IAM permissions to create EC2 instances, IAM roles, instance profiles, and security groups (aws configure or a named profile)

Cost: An r5.xlarge in us-east-2 runs at roughly $0.25/hour. The SUSE SLES for SAP PAYG AMI includes the SUSE subscription in that cost — no separate licence to worry about. A full deployment and verification run takes under an hour, so a single test should cost you around $0.25–$0.50. Just remember to terminate the instance when you’re done.

Architecture

React UI (browser)


Node.js / Express (server.js)
├── Terraform (provision EC2)
└── Ansible (install HANA via SSH)


AWS EC2 (SUSE SLES for SAP 15 SP5 PAYG)

└── S3 (HANA media)

Infrastructure — Terraform

Why SUSE SLES for SAP PAYG?

HANA requires SUSE Linux Enterprise Server for SAP Applications — not standard SLES, and not CentOS/RHEL. The PAYG (pay-as-you-go) AMI is the right choice on AWS because it includes the SUSE subscription built into the instance cost. BYOS (bring your own subscription) requires a separate SUSE registration step that complicates automation.

For us-east-2 (Ohio):

locals {
sles_sap_ami = “ami-099afc29551c4b139” # SUSE SLES for SAP 15 SP5 PAYG, us-east-2
}

Minimum Viable Instance for HANA

SAP’s official minimum for HANA is 32GB RAM. The smallest AWS instance that meets this is r5.xlarge (4 vCPU, 32GB). It’s not certified for production but works fine for dev/test.

resource “aws_instance” “hana” {
ami = local.sles_sap_ami
instance_type = “r5.xlarge”
key_name = var.key_pair_name
vpc_security_group_ids = [aws_security_group.hana.id]
iam_instance_profile = aws_iam_instance_profile.hana_s3.name

root_block_device {
volume_type = “gp3”
volume_size = 100 # 50GB isn’t enough — HANA media (15GB) + install fills it
}

tags = { Name = “hana-server” }
}

Gotcha: Start with at least 100GB. The HANA media alone is ~15GB and the installation needs room to expand. A 50GB volume will fail mid-install with a disk full error.

IAM — S3 Access for HANA Media

Rather than baking credentials into the instance, we attach an IAM instance profile that grants read access to the S3 bucket holding the HANA media:

resource “aws_iam_role_policy” “hana_s3” {
name = “hana-s3-read”
role = aws_iam_role.hana_s3.id

policy = jsonencode({
Version = “2012-10-17”
Statement = [{
Effect = “Allow”
Action = [“s3:GetObject”, “s3:ListBucket”]
Resource = [
“arn:aws:s3:::${var.hana_media_bucket}”,
“arn:aws:s3:::${var.hana_media_bucket}/*”
]
}]
})
}

HANA Media — S3

Upload the HANA media once. The Ansible playbook pulls it down on each deployment:

aws s3 sync ~/Downloads/51058674/ s3://your-hana-bucket/SAP_HANA_DATABASE/
–region us-east-2 –no-progress –exclude “*.DS_Store”

The hdblcm installer lives at:

s3://your-hana-bucket/SAP_HANA_DATABASE/DATA_UNITS/HDB_SERVER_LINUX_X86_64/hdblcm

The Ansible Playbook

OS Prerequisites

HANA has specific OS requirements. Key tasks:

Swap:

– name: Create swapfile
command: dd if=/dev/zero of=/swapfile bs=1M count={{ swap_size_mb }}
when: swap_check.stdout == “”

Kernel parameters:

– name: Set kernel parameters for HANA
sysctl:
name: “{{ item.key }}”
value: “{{ item.value }}”
sysctl_set: true
reload: true
loop:
– { key: “vm.max_map_count”, value: “2147483647” }
– { key: “kernel.sem”, value: “1250 256000 100 8192” }
– { key: “kernel.numa_balancing”, value: “0” }

Transparent Huge Pages — must be disabled:

– name: Disable THP at runtime
shell: |
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

saptune (SUSE’s SAP tuning tool):

– name: Apply SAP HANA tuning profile
command: saptune solution apply HANA

Pulling HANA Media from S3

The instance profile handles authentication — no keys needed:

– name: Sync HANA media from S3
command: >
aws s3 sync s3://{{ hana_s3_bucket }}/{{ hana_s3_prefix }}/ {{ hana_media_dir }}/
–no-progress

The Silent Install — hdblcm Response File

HANA’s installer (hdblcm) supports unattended installation via a response file. We generate it with a Jinja2 template rather than Ansible’s copy module — the copy module’s YAML block scalar adds leading whitespace that hdblcm’s parser chokes on.

hana_install.cfg.j2:

[Server]
action=install
components=server
sid={{ hana_sid }}
number={{ hana_instance }}
password={{ hana_master_pw }}
sapadm_password={{ sapadm_password }}
system_user_password={{ hana_master_pw }}
lss_user_password={{ lss_password }}
lss_backup_password={{ lss_password }}
system_usage=custom
install_execution_mode=optimized
sapmnt={{ hana_install_dir }}
datapath={{ hana_data_dir }}/{{ hana_sid }}
logpath={{ hana_log_dir }}/{{ hana_sid }}
ignore=check_min_mem- name: Write hdblcm silent response file
template:
src: hana_install.cfg.j2
dest: /tmp/hana_install.cfg
mode: “0600”

Gotcha: The lss_user_password and lss_backup_password fields are required from HANA 2.0 SPS06 onwards. LSS (Log Segment Store) was optional in earlier releases but is now mandatory. If you omit these fields, hdblcm will fail silently with a cryptic parameter error.

Moving LSS Media Aside

The HANA media bundle includes an LSS component folder. If hdblcm detects it, it tries to install it and expects additional parameters. Moving it aside before running the installer avoids this:

– name: Move LSS media aside
command: mv {{ hana_media_dir }}/lss {{ hana_media_dir }}/lss_aside
args:
removes: “{{ hana_media_dir }}/lss”

Running hdblcm — Async is Essential

The HANA installation takes 20–40 minutes. Without async, the SSH connection will time out and kill the install mid-way:

– name: Run SAP HANA installation (hdblcm)
command: >
{{ hdblcm_path }} –batch –configfile=/tmp/hana_install.cfg
register: hdblcm_result
changed_when: true
async: 3600
poll: 30

async: 3600 lets the task run for up to an hour. poll: 30 checks back every 30 seconds. Ansible keeps the job alive on the remote end even if the control connection drops.

Verify Installation

– name: Check HDB daemon is running
shell: |
su – {{ hana_sid | lower }}adm -c “HDB info” 2>&1 | grep -E “hdbdaemon|hdbnameserver”
register: hdb_info
failed_when: hdb_info.rc != 0

Streaming Output to the Browser

The Node.js backend uses child_process.spawn to run both Terraform and Ansible, streaming stdout/stderr as chunked HTML to an iframe. This means you can watch the install progress in real time without polling.

const proc = spawn(‘ansible-playbook’, [
‘-i’, `ec2-user@${ip},`,
‘–private-key’, keyPath,
‘-e’, extraVars,
playbookPath
], { env: { …process.env, ANSIBLE_FORCE_COLOR: ‘1’ } });

proc.stdout.on(‘data’, chunk => {
res.write(`<pre>${chunk}</pre>`);
});

Key Lessons

Problem Fix

BYOS AMI requires SUSE registrationUse PAYG AMI instead50GB disk fills during installUse 100GB minimumSSH timeout kills hdblcmasync: 3600, poll: 30hdblcm config with leading spacesUse template module, not copylss_user_password missingRequired from SPS06+ — include ithdblcm finds LSS media and failsMove lss folder aside before installmacOS SSH key permission deniedMove PEM to ~/.ssh/, xattr -c, chmod 400

Result

A fully automated HANA 2.0 SPS08 installation on a fresh SUSE SLES for SAP instance in under 40 minutes, triggered from a browser. The same pattern extends to NetWeaver, S/4HANA, or any other SAP component that supports hdblcm or sapinst unattended install.

Appendix A — Full Ansible Playbook

server/ansible/hana-install.yml


– name: SAP HANA prerequisites and installation
hosts: all
become: true
gather_facts: true

vars:
hana_sid: “HDB”
hana_instance: “00”
hana_master_pw: “<your-hana-master-password>”
sapadm_password: “<your-sapadm-password>”
lss_password: “<your-lss-password>”
hana_s3_bucket: “<your-s3-bucket-name>”
hana_s3_prefix: “SAP_HANA_DATABASE/DATA_UNITS/HDB_SERVER_LINUX_X86_64”
hana_media_dir: “/hana/media”
hana_install_dir: “/hana/shared”
hana_data_dir: “/hana/data”
hana_log_dir: “/hana/log”
swap_size_mb: 20480

tasks:

– name: Confirm target is SUSE
fail:
msg: “This playbook requires SUSE Linux Enterprise Server. Got: {{ ansible_os_family }}”
when: ansible_os_family != ‘Suse’

– name: Refresh zypper repositories
zypper_repository:
repo: ‘*’
runrefresh: true
ignore_errors: true

– name: Install required OS packages
zypper:
name:
– libaio1
– libaio-devel
– libltdl7
– libatomic1
– libnuma1
– numactl
– uuidd
– rsync
– lsof
– unzip
– xfsprogs
– glibc-i18ndata
– graphviz
– expect
– python3
state: present

– name: Enable and start uuidd
systemd:
name: uuidd
state: started
enabled: true

– name: Check existing swap
command: swapon –show=SIZE –noheadings –bytes
register: swap_check
changed_when: false
failed_when: false

– name: Create swapfile
command: dd if=/dev/zero of=/swapfile bs=1M count={{ swap_size_mb }}
when: swap_check.stdout == “”

– name: Set swapfile permissions
file:
path: /swapfile
mode: “0600”
when: swap_check.stdout == “”

– name: Format swapfile
command: mkswap /swapfile
when: swap_check.stdout == “”

– name: Activate swapfile
command: swapon /swapfile
when: swap_check.stdout == “”

– name: Persist swapfile in fstab
lineinfile:
path: /etc/fstab
line: “/swapfile swap swap defaults 0 0”
state: present

– name: Set kernel parameters for HANA
sysctl:
name: “{{ item.key }}”
value: “{{ item.value }}”
sysctl_set: true
state: present
reload: true
loop:
– { key: “vm.max_map_count”, value: “2147483647” }
– { key: “kernel.sem”, value: “1250 256000 100 8192” }
– { key: “net.core.somaxconn”, value: “4096” }
– { key: “net.ipv4.tcp_max_syn_backlog”, value: “8192” }
– { key: “kernel.numa_balancing”, value: “0” }

– name: Set ulimits for SAP
copy:
dest: /etc/security/limits.d/99-sap.conf
mode: “0644”
content: |
@sapsys soft nofile 1048576
@sapsys hard nofile 1048576
@sapsys soft nproc unlimited
@sapsys hard nproc unlimited
@sdba soft nofile 1048576
@sdba hard nofile 1048576

– name: Disable THP at runtime
shell: |
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
changed_when: true

– name: Create systemd unit to disable THP on boot
copy:
dest: /etc/systemd/system/disable-thp.service
mode: “0644”
content: |
[Unit]
Description=Disable Transparent Huge Pages
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c “echo never > /sys/kernel/mm/transparent_hugepage/enabled && echo never > /sys/kernel/mm/transparent_hugepage/defrag”
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

– name: Enable disable-thp service
systemd:
name: disable-thp
enabled: true
daemon_reload: true

– name: Create HANA filesystem directories
file:
path: “{{ item }}”
state: directory
mode: “0755”
loop:
– “{{ hana_install_dir }}”
– “{{ hana_data_dir }}”
– “{{ hana_log_dir }}”
– “/hana/backup”

– name: Install saptune
zypper:
name: saptune
state: present
ignore_errors: true

– name: Apply SAP HANA tuning profile via saptune
command: saptune solution apply HANA
changed_when: true
ignore_errors: true

– name: Enable saptune daemon
systemd:
name: saptune
enabled: true
state: started
ignore_errors: true

– name: Check if aws cli is present
command: which aws
register: aws_cli_check
changed_when: false
failed_when: false

– name: Download and install AWS CLI v2
shell: |
curl -fsSL “https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip” -o /tmp/awscliv2.zip
unzip -q /tmp/awscliv2.zip -d /tmp/awscli-install
/tmp/awscli-install/aws/install –bin-dir /usr/local/bin –install-dir /usr/local/aws-cli –update
rm -rf /tmp/awscliv2.zip /tmp/awscli-install
when: aws_cli_check.rc != 0

– name: Create remote media staging directory
file:
path: “{{ hana_media_dir }}”
state: directory
mode: “0755”

– name: Sync HANA media from S3
command: >
aws s3 sync s3://{{ hana_s3_bucket }}/{{ hana_s3_prefix }}/ {{ hana_media_dir }}/
–no-progress
register: s3_sync
changed_when: “‘download’ in s3_sync.stdout”

– name: Find hdblcm installer
find:
paths: “{{ hana_media_dir }}”
patterns: “hdblcm”
recurse: true
register: hdblcm_find

– name: Fail if hdblcm not found
fail:
msg: “hdblcm not found under {{ hana_media_dir }} — check hana_s3_bucket and hana_s3_prefix”
when: hdblcm_find.files | length == 0

– name: Set hdblcm path fact
set_fact:
hdblcm_path: “{{ hdblcm_find.files[0].path }}”

– name: Make media directory fully executable
shell: chmod -R 755 {{ hana_media_dir }}
changed_when: true

– name: Move LSS media aside
command: mv {{ hana_media_dir }}/lss {{ hana_media_dir }}/lss_aside
args:
removes: “{{ hana_media_dir }}/lss”

– name: Write hdblcm silent response file
template:
src: hana_install.cfg.j2
dest: /tmp/hana_install.cfg
mode: “0600”

– name: Run SAP HANA installation (hdblcm)
command: >
{{ hdblcm_path }} –batch –configfile=/tmp/hana_install.cfg
register: hdblcm_result
changed_when: true
async: 3600
poll: 30

– name: Show hdblcm output
debug:
msg: “{{ hdblcm_result.stdout_lines }}”

– name: Check HDB daemon is running
shell: |
su – {{ hana_sid | lower }}adm -c “HDB info” 2>&1 | grep -E “hdbdaemon|hdbnameserver”
register: hdb_info
changed_when: false
failed_when: hdb_info.rc != 0

– name: Confirm HANA installation complete
debug:
msg: >
SAP HANA {{ hana_sid }} instance {{ hana_instance }} installed successfully.
Connect: hdbsql -i {{ hana_instance }} -u SYSTEM -p <password>

Appendix B — Terraform Configuration

main.tf

locals {
sles_sap_ami = “ami-099afc29551c4b139” # SUSE SLES for SAP 15 SP5 PAYG, us-east-2
}

resource “aws_iam_role” “hana_s3” {
name = “hana-s3-role”

assume_role_policy = jsonencode({
Version = “2012-10-17”
Statement = [{
Effect = “Allow”
Principal = { Service = “ec2.amazonaws.com” }
Action = “sts:AssumeRole”
}]
})
}

resource “aws_iam_role_policy” “hana_s3” {
name = “hana-s3-read”
role = aws_iam_role.hana_s3.id

policy = jsonencode({
Version = “2012-10-17”
Statement = [{
Effect = “Allow”
Action = [“s3:GetObject”, “s3:ListBucket”]
Resource = [
“arn:aws:s3:::${var.hana_media_bucket}”,
“arn:aws:s3:::${var.hana_media_bucket}/*”
]
}]
})
}

resource “aws_iam_instance_profile” “hana_s3” {
name = “hana-s3-profile”
role = aws_iam_role.hana_s3.name
}

resource “aws_security_group” “hana” {
name = “hana-sg”
description = “SSH access for HANA deployment”

ingress {
from_port = 22
to_port = 22
protocol = “tcp”
cidr_blocks = [“0.0.0.0/0”]
}

egress {
from_port = 0
to_port = 0
protocol = “-1”
cidr_blocks = [“0.0.0.0/0”]
}

tags = { Name = “hana-sg” }
}

resource “aws_instance” “hana” {
ami = local.sles_sap_ami
instance_type = “r5.xlarge”
key_name = var.key_pair_name
vpc_security_group_ids = [aws_security_group.hana.id]
iam_instance_profile = aws_iam_instance_profile.hana_s3.name

root_block_device {
volume_type = “gp3”
volume_size = 100
}

tags = { Name = “hana-server” }
}

variables.tf

variable “aws_region” {
default = “us-east-2”
}

variable “key_pair_name” {
default = “<your-key-pair-name>”
}

variable “hana_media_bucket” {
default = “<your-s3-bucket-name>”
}

outputs.tf

output “public_ip” {
value = aws_instance.hana.public_ip
}

output “ssh_command” {
value = “ssh -i ${var.key_pair_name}.pem ec2-user@${aws_instance.hana.public_ip}”
}

providers.tf

terraform {
required_providers {
aws = {
source = “hashicorp/aws”
version = “~> 5.0”
}
}
}

provider “aws” {
region = var.aws_region
}

hana_install.cfg.j2

[Server]
action=install
components=server
sid={{ hana_sid }}
number={{ hana_instance }}
password={{ hana_master_pw }}
sapadm_password={{ sapadm_password }}
system_user_password={{ hana_master_pw }}
lss_user_password={{ lss_password }}
lss_backup_password={{ lss_password }}
system_usage=custom
install_execution_mode=optimized
sapmnt={{ hana_install_dir }}
datapath={{ hana_data_dir }}/{{ hana_sid }}
logpath={{ hana_log_dir }}/{{ hana_sid }}
ignore=check_min_mem

 

 

​ IntroductionThis post is a trimmed-down version of the core automation work from my MSc final project, adapted here as a practical walkthrough for the SAP Community. I’ve left out the React UI side of things — Terraform and Ansible are dyed-in-the-wool command line tools, and covering the UI layer in full would make this a very different (and much longer) post.The goal was a fully automated SAP HANA 2.0 SPS08 deployment on AWS, driven from a React web UI. Terraform provisions the infrastructure, Ansible handles the HANA installation, and a Node.js backend streams the output live to the browser. PrerequisitesYou’ll need the following installed and configured locally before you start:Terraform (v1.0+) — terraform.ioAnsible (v2.12+) — pip install ansibleAWS CLI v2 — aws.amazon.com/cliAWS credentials configured with sufficient IAM permissions to create EC2 instances, IAM roles, instance profiles, and security groups (aws configure or a named profile)Cost: An r5.xlarge in us-east-2 runs at roughly $0.25/hour. The SUSE SLES for SAP PAYG AMI includes the SUSE subscription in that cost — no separate licence to worry about. A full deployment and verification run takes under an hour, so a single test should cost you around $0.25–$0.50. Just remember to terminate the instance when you’re done.ArchitectureReact UI (browser)


Node.js / Express (server.js)
├── Terraform (provision EC2)
└── Ansible (install HANA via SSH)


AWS EC2 (SUSE SLES for SAP 15 SP5 PAYG)

└── S3 (HANA media)Infrastructure — TerraformWhy SUSE SLES for SAP PAYG?HANA requires SUSE Linux Enterprise Server for SAP Applications — not standard SLES, and not CentOS/RHEL. The PAYG (pay-as-you-go) AMI is the right choice on AWS because it includes the SUSE subscription built into the instance cost. BYOS (bring your own subscription) requires a separate SUSE registration step that complicates automation.For us-east-2 (Ohio):locals {
sles_sap_ami = “ami-099afc29551c4b139” # SUSE SLES for SAP 15 SP5 PAYG, us-east-2
}Minimum Viable Instance for HANASAP’s official minimum for HANA is 32GB RAM. The smallest AWS instance that meets this is r5.xlarge (4 vCPU, 32GB). It’s not certified for production but works fine for dev/test.resource “aws_instance” “hana” {
ami = local.sles_sap_ami
instance_type = “r5.xlarge”
key_name = var.key_pair_name
vpc_security_group_ids = [aws_security_group.hana.id]
iam_instance_profile = aws_iam_instance_profile.hana_s3.name

root_block_device {
volume_type = “gp3”
volume_size = 100 # 50GB isn’t enough — HANA media (15GB) + install fills it
}

tags = { Name = “hana-server” }
}Gotcha: Start with at least 100GB. The HANA media alone is ~15GB and the installation needs room to expand. A 50GB volume will fail mid-install with a disk full error.IAM — S3 Access for HANA MediaRather than baking credentials into the instance, we attach an IAM instance profile that grants read access to the S3 bucket holding the HANA media:resource “aws_iam_role_policy” “hana_s3” {
name = “hana-s3-read”
role = aws_iam_role.hana_s3.id

policy = jsonencode({
Version = “2012-10-17”
Statement = [{
Effect = “Allow”
Action = [“s3:GetObject”, “s3:ListBucket”]
Resource = [
“arn:aws:s3:::${var.hana_media_bucket}”,
“arn:aws:s3:::${var.hana_media_bucket}/*”
]
}]
})
}HANA Media — S3Upload the HANA media once. The Ansible playbook pulls it down on each deployment:aws s3 sync ~/Downloads/51058674/ s3://your-hana-bucket/SAP_HANA_DATABASE/
–region us-east-2 –no-progress –exclude “*.DS_Store”The hdblcm installer lives at:s3://your-hana-bucket/SAP_HANA_DATABASE/DATA_UNITS/HDB_SERVER_LINUX_X86_64/hdblcmThe Ansible PlaybookOS PrerequisitesHANA has specific OS requirements. Key tasks:Swap:- name: Create swapfile
command: dd if=/dev/zero of=/swapfile bs=1M count={{ swap_size_mb }}
when: swap_check.stdout == “”Kernel parameters:- name: Set kernel parameters for HANA
sysctl:
name: “{{ item.key }}”
value: “{{ item.value }}”
sysctl_set: true
reload: true
loop:
– { key: “vm.max_map_count”, value: “2147483647” }
– { key: “kernel.sem”, value: “1250 256000 100 8192” }
– { key: “kernel.numa_balancing”, value: “0” }Transparent Huge Pages — must be disabled:- name: Disable THP at runtime
shell: |
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defragsaptune (SUSE’s SAP tuning tool):- name: Apply SAP HANA tuning profile
command: saptune solution apply HANAPulling HANA Media from S3The instance profile handles authentication — no keys needed:- name: Sync HANA media from S3
command: >
aws s3 sync s3://{{ hana_s3_bucket }}/{{ hana_s3_prefix }}/ {{ hana_media_dir }}/
–no-progressThe Silent Install — hdblcm Response FileHANA’s installer (hdblcm) supports unattended installation via a response file. We generate it with a Jinja2 template rather than Ansible’s copy module — the copy module’s YAML block scalar adds leading whitespace that hdblcm’s parser chokes on.hana_install.cfg.j2:[Server]
action=install
components=server
sid={{ hana_sid }}
number={{ hana_instance }}
password={{ hana_master_pw }}
sapadm_password={{ sapadm_password }}
system_user_password={{ hana_master_pw }}
lss_user_password={{ lss_password }}
lss_backup_password={{ lss_password }}
system_usage=custom
install_execution_mode=optimized
sapmnt={{ hana_install_dir }}
datapath={{ hana_data_dir }}/{{ hana_sid }}
logpath={{ hana_log_dir }}/{{ hana_sid }}
ignore=check_min_mem- name: Write hdblcm silent response file
template:
src: hana_install.cfg.j2
dest: /tmp/hana_install.cfg
mode: “0600”Gotcha: The lss_user_password and lss_backup_password fields are required from HANA 2.0 SPS06 onwards. LSS (Log Segment Store) was optional in earlier releases but is now mandatory. If you omit these fields, hdblcm will fail silently with a cryptic parameter error.Moving LSS Media AsideThe HANA media bundle includes an LSS component folder. If hdblcm detects it, it tries to install it and expects additional parameters. Moving it aside before running the installer avoids this:- name: Move LSS media aside
command: mv {{ hana_media_dir }}/lss {{ hana_media_dir }}/lss_aside
args:
removes: “{{ hana_media_dir }}/lss”Running hdblcm — Async is EssentialThe HANA installation takes 20–40 minutes. Without async, the SSH connection will time out and kill the install mid-way:- name: Run SAP HANA installation (hdblcm)
command: >
{{ hdblcm_path }} –batch –configfile=/tmp/hana_install.cfg
register: hdblcm_result
changed_when: true
async: 3600
poll: 30async: 3600 lets the task run for up to an hour. poll: 30 checks back every 30 seconds. Ansible keeps the job alive on the remote end even if the control connection drops.Verify Installation- name: Check HDB daemon is running
shell: |
su – {{ hana_sid | lower }}adm -c “HDB info” 2>&1 | grep -E “hdbdaemon|hdbnameserver”
register: hdb_info
failed_when: hdb_info.rc != 0Streaming Output to the BrowserThe Node.js backend uses child_process.spawn to run both Terraform and Ansible, streaming stdout/stderr as chunked HTML to an iframe. This means you can watch the install progress in real time without polling.const proc = spawn(‘ansible-playbook’, [
‘-i’, `ec2-user@${ip},`,
‘–private-key’, keyPath,
‘-e’, extraVars,
playbookPath
], { env: { …process.env, ANSIBLE_FORCE_COLOR: ‘1’ } });

proc.stdout.on(‘data’, chunk => {
res.write(`<pre>${chunk}</pre>`);
});Key LessonsProblem FixBYOS AMI requires SUSE registrationUse PAYG AMI instead50GB disk fills during installUse 100GB minimumSSH timeout kills hdblcmasync: 3600, poll: 30hdblcm config with leading spacesUse template module, not copylss_user_password missingRequired from SPS06+ — include ithdblcm finds LSS media and failsMove lss folder aside before installmacOS SSH key permission deniedMove PEM to ~/.ssh/, xattr -c, chmod 400ResultA fully automated HANA 2.0 SPS08 installation on a fresh SUSE SLES for SAP instance in under 40 minutes, triggered from a browser. The same pattern extends to NetWeaver, S/4HANA, or any other SAP component that supports hdblcm or sapinst unattended install.Appendix A — Full Ansible Playbookserver/ansible/hana-install.yml—
– name: SAP HANA prerequisites and installation
hosts: all
become: true
gather_facts: true

vars:
hana_sid: “HDB”
hana_instance: “00”
hana_master_pw: “<your-hana-master-password>”
sapadm_password: “<your-sapadm-password>”
lss_password: “<your-lss-password>”
hana_s3_bucket: “<your-s3-bucket-name>”
hana_s3_prefix: “SAP_HANA_DATABASE/DATA_UNITS/HDB_SERVER_LINUX_X86_64”
hana_media_dir: “/hana/media”
hana_install_dir: “/hana/shared”
hana_data_dir: “/hana/data”
hana_log_dir: “/hana/log”
swap_size_mb: 20480

tasks:

– name: Confirm target is SUSE
fail:
msg: “This playbook requires SUSE Linux Enterprise Server. Got: {{ ansible_os_family }}”
when: ansible_os_family != ‘Suse’

– name: Refresh zypper repositories
zypper_repository:
repo: ‘*’
runrefresh: true
ignore_errors: true

– name: Install required OS packages
zypper:
name:
– libaio1
– libaio-devel
– libltdl7
– libatomic1
– libnuma1
– numactl
– uuidd
– rsync
– lsof
– unzip
– xfsprogs
– glibc-i18ndata
– graphviz
– expect
– python3
state: present

– name: Enable and start uuidd
systemd:
name: uuidd
state: started
enabled: true

– name: Check existing swap
command: swapon –show=SIZE –noheadings –bytes
register: swap_check
changed_when: false
failed_when: false

– name: Create swapfile
command: dd if=/dev/zero of=/swapfile bs=1M count={{ swap_size_mb }}
when: swap_check.stdout == “”

– name: Set swapfile permissions
file:
path: /swapfile
mode: “0600”
when: swap_check.stdout == “”

– name: Format swapfile
command: mkswap /swapfile
when: swap_check.stdout == “”

– name: Activate swapfile
command: swapon /swapfile
when: swap_check.stdout == “”

– name: Persist swapfile in fstab
lineinfile:
path: /etc/fstab
line: “/swapfile swap swap defaults 0 0”
state: present

– name: Set kernel parameters for HANA
sysctl:
name: “{{ item.key }}”
value: “{{ item.value }}”
sysctl_set: true
state: present
reload: true
loop:
– { key: “vm.max_map_count”, value: “2147483647” }
– { key: “kernel.sem”, value: “1250 256000 100 8192” }
– { key: “net.core.somaxconn”, value: “4096” }
– { key: “net.ipv4.tcp_max_syn_backlog”, value: “8192” }
– { key: “kernel.numa_balancing”, value: “0” }

– name: Set ulimits for SAP
copy:
dest: /etc/security/limits.d/99-sap.conf
mode: “0644”
content: |
@sapsys soft nofile 1048576
@sapsys hard nofile 1048576
@sapsys soft nproc unlimited
@sapsys hard nproc unlimited
@sdba soft nofile 1048576
@sdba hard nofile 1048576

– name: Disable THP at runtime
shell: |
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
changed_when: true

– name: Create systemd unit to disable THP on boot
copy:
dest: /etc/systemd/system/disable-thp.service
mode: “0644”
content: |
[Unit]
Description=Disable Transparent Huge Pages
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c “echo never > /sys/kernel/mm/transparent_hugepage/enabled && echo never > /sys/kernel/mm/transparent_hugepage/defrag”
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

– name: Enable disable-thp service
systemd:
name: disable-thp
enabled: true
daemon_reload: true

– name: Create HANA filesystem directories
file:
path: “{{ item }}”
state: directory
mode: “0755”
loop:
– “{{ hana_install_dir }}”
– “{{ hana_data_dir }}”
– “{{ hana_log_dir }}”
– “/hana/backup”

– name: Install saptune
zypper:
name: saptune
state: present
ignore_errors: true

– name: Apply SAP HANA tuning profile via saptune
command: saptune solution apply HANA
changed_when: true
ignore_errors: true

– name: Enable saptune daemon
systemd:
name: saptune
enabled: true
state: started
ignore_errors: true

– name: Check if aws cli is present
command: which aws
register: aws_cli_check
changed_when: false
failed_when: false

– name: Download and install AWS CLI v2
shell: |
curl -fsSL “https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip” -o /tmp/awscliv2.zip
unzip -q /tmp/awscliv2.zip -d /tmp/awscli-install
/tmp/awscli-install/aws/install –bin-dir /usr/local/bin –install-dir /usr/local/aws-cli –update
rm -rf /tmp/awscliv2.zip /tmp/awscli-install
when: aws_cli_check.rc != 0

– name: Create remote media staging directory
file:
path: “{{ hana_media_dir }}”
state: directory
mode: “0755”

– name: Sync HANA media from S3
command: >
aws s3 sync s3://{{ hana_s3_bucket }}/{{ hana_s3_prefix }}/ {{ hana_media_dir }}/
–no-progress
register: s3_sync
changed_when: “‘download’ in s3_sync.stdout”

– name: Find hdblcm installer
find:
paths: “{{ hana_media_dir }}”
patterns: “hdblcm”
recurse: true
register: hdblcm_find

– name: Fail if hdblcm not found
fail:
msg: “hdblcm not found under {{ hana_media_dir }} — check hana_s3_bucket and hana_s3_prefix”
when: hdblcm_find.files | length == 0

– name: Set hdblcm path fact
set_fact:
hdblcm_path: “{{ hdblcm_find.files[0].path }}”

– name: Make media directory fully executable
shell: chmod -R 755 {{ hana_media_dir }}
changed_when: true

– name: Move LSS media aside
command: mv {{ hana_media_dir }}/lss {{ hana_media_dir }}/lss_aside
args:
removes: “{{ hana_media_dir }}/lss”

– name: Write hdblcm silent response file
template:
src: hana_install.cfg.j2
dest: /tmp/hana_install.cfg
mode: “0600”

– name: Run SAP HANA installation (hdblcm)
command: >
{{ hdblcm_path }} –batch –configfile=/tmp/hana_install.cfg
register: hdblcm_result
changed_when: true
async: 3600
poll: 30

– name: Show hdblcm output
debug:
msg: “{{ hdblcm_result.stdout_lines }}”

– name: Check HDB daemon is running
shell: |
su – {{ hana_sid | lower }}adm -c “HDB info” 2>&1 | grep -E “hdbdaemon|hdbnameserver”
register: hdb_info
changed_when: false
failed_when: hdb_info.rc != 0

– name: Confirm HANA installation complete
debug:
msg: >
SAP HANA {{ hana_sid }} instance {{ hana_instance }} installed successfully.
Connect: hdbsql -i {{ hana_instance }} -u SYSTEM -p <password>Appendix B — Terraform Configurationmain.tflocals {
sles_sap_ami = “ami-099afc29551c4b139” # SUSE SLES for SAP 15 SP5 PAYG, us-east-2
}

resource “aws_iam_role” “hana_s3” {
name = “hana-s3-role”

assume_role_policy = jsonencode({
Version = “2012-10-17”
Statement = [{
Effect = “Allow”
Principal = { Service = “ec2.amazonaws.com” }
Action = “sts:AssumeRole”
}]
})
}

resource “aws_iam_role_policy” “hana_s3” {
name = “hana-s3-read”
role = aws_iam_role.hana_s3.id

policy = jsonencode({
Version = “2012-10-17”
Statement = [{
Effect = “Allow”
Action = [“s3:GetObject”, “s3:ListBucket”]
Resource = [
“arn:aws:s3:::${var.hana_media_bucket}”,
“arn:aws:s3:::${var.hana_media_bucket}/*”
]
}]
})
}

resource “aws_iam_instance_profile” “hana_s3” {
name = “hana-s3-profile”
role = aws_iam_role.hana_s3.name
}

resource “aws_security_group” “hana” {
name = “hana-sg”
description = “SSH access for HANA deployment”

ingress {
from_port = 22
to_port = 22
protocol = “tcp”
cidr_blocks = [“0.0.0.0/0”]
}

egress {
from_port = 0
to_port = 0
protocol = “-1”
cidr_blocks = [“0.0.0.0/0”]
}

tags = { Name = “hana-sg” }
}

resource “aws_instance” “hana” {
ami = local.sles_sap_ami
instance_type = “r5.xlarge”
key_name = var.key_pair_name
vpc_security_group_ids = [aws_security_group.hana.id]
iam_instance_profile = aws_iam_instance_profile.hana_s3.name

root_block_device {
volume_type = “gp3”
volume_size = 100
}

tags = { Name = “hana-server” }
}variables.tfvariable “aws_region” {
default = “us-east-2”
}

variable “key_pair_name” {
default = “<your-key-pair-name>”
}

variable “hana_media_bucket” {
default = “<your-s3-bucket-name>”
}outputs.tfoutput “public_ip” {
value = aws_instance.hana.public_ip
}

output “ssh_command” {
value = “ssh -i ${var.key_pair_name}.pem ec2-user@${aws_instance.hana.public_ip}”
}providers.tfterraform {
required_providers {
aws = {
source = “hashicorp/aws”
version = “~> 5.0”
}
}
}

provider “aws” {
region = var.aws_region
}hana_install.cfg.j2[Server]
action=install
components=server
sid={{ hana_sid }}
number={{ hana_instance }}
password={{ hana_master_pw }}
sapadm_password={{ sapadm_password }}
system_user_password={{ hana_master_pw }}
lss_user_password={{ lss_password }}
lss_backup_password={{ lss_password }}
system_usage=custom
install_execution_mode=optimized
sapmnt={{ hana_install_dir }}
datapath={{ hana_data_dir }}/{{ hana_sid }}
logpath={{ hana_log_dir }}/{{ hana_sid }}
ignore=check_min_mem    Read More Technology Blog Posts by Members articles 

#SAP

#SAPTechnologyblog

You May Also Like

More From Author