You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by GitBox <gi...@apache.org> on 2018/01/02 08:49:16 UTC

[GitHub] merlimat closed pull request #920: Provide an Ansible playbook for AWS with documentation

merlimat closed pull request #920: Provide an Ansible playbook for AWS with documentation
URL: https://github.com/apache/incubator-pulsar/pull/920
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/.gitignore b/.gitignore
index 7583b29f8..1e194b2a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,9 @@ target/
 
 # Generated website
 generated-site/
+
+# Ansible and Terraform artifacts
+deployment/terraform-ansible/deploy-pulsar.retry
+deployment/terraform-ansible/aws/terraform*
+deployment/terraform-ansible/aws/.terraform/
+deployment/terraform-ansible/aws/.terraform.tfstate.lock.info
diff --git a/deployment/terraform-ansible/aws/ansible.cfg b/deployment/terraform-ansible/aws/ansible.cfg
new file mode 100644
index 000000000..69cf91f13
--- /dev/null
+++ b/deployment/terraform-ansible/aws/ansible.cfg
@@ -0,0 +1,9 @@
+[defaults]
+private_key_file=~/.ssh/pulsar_aws
+host_key_checking=false
+user='ec2-user'
+
+[privilege_escalation]
+become=True
+become_method='sudo'
+become_user='root'
diff --git a/deployment/terraform-ansible/aws/instances.tf b/deployment/terraform-ansible/aws/instances.tf
new file mode 100644
index 000000000..079660f3a
--- /dev/null
+++ b/deployment/terraform-ansible/aws/instances.tf
@@ -0,0 +1,27 @@
+resource "aws_instance" "zookeeper" {
+  ami                    = "${var.aws_ami}"
+  instance_type          = "${var.instance_types["zookeeper"]}"
+  key_name               = "${aws_key_pair.default.id}"
+  subnet_id              = "${aws_subnet.default.id}"
+  vpc_security_group_ids = ["${aws_security_group.default.id}"]
+  count                  = "${var.num_zookeeper_nodes}"
+
+  tags {
+    Name = "zookeeper-${count.index + 1}"
+  }
+}
+
+resource "aws_instance" "pulsar" {
+  ami                         = "${var.aws_ami}"
+  instance_type               = "${var.instance_types["pulsar"]}"
+  key_name                    = "${aws_key_pair.default.id}"
+  subnet_id                   = "${aws_subnet.default.id}"
+  vpc_security_group_ids      = ["${aws_security_group.default.id}"]
+  count                       = "${var.num_pulsar_brokers}"
+
+  tags {
+    Name = "pulsar-${count.index + 1}"
+  }
+
+  associate_public_ip_address = true
+}
diff --git a/deployment/terraform-ansible/aws/keys.tf b/deployment/terraform-ansible/aws/keys.tf
new file mode 100644
index 000000000..808cee451
--- /dev/null
+++ b/deployment/terraform-ansible/aws/keys.tf
@@ -0,0 +1,9 @@
+resource "random_id" "key_pair_name" {
+  byte_length = 4
+  prefix      = "${var.key_name_prefix}-"
+}
+
+resource "aws_key_pair" "default" {
+  key_name   = "${random_id.key_pair_name.hex}"
+  public_key = "${file(var.public_key_path)}"
+}
\ No newline at end of file
diff --git a/deployment/terraform-ansible/aws/network.tf b/deployment/terraform-ansible/aws/network.tf
new file mode 100644
index 000000000..7a97dc01a
--- /dev/null
+++ b/deployment/terraform-ansible/aws/network.tf
@@ -0,0 +1,98 @@
+resource "aws_vpc" "pulsar_vpc" {
+  cidr_block           = "${var.base_cidr_block}"
+  enable_dns_support   = true
+  enable_dns_hostnames = true
+
+  tags {
+    Name = "Pulsar-VPC"
+  }
+}
+
+resource "aws_subnet" "default" {
+  vpc_id                  = "${aws_vpc.pulsar_vpc.id}"
+  cidr_block              = "${cidrsubnet(var.base_cidr_block, 8, 2)}"
+  availability_zone       = "${var.availability_zone}"
+  map_public_ip_on_launch = true
+
+  tags {
+    Name = "Pulsar-Subnet"
+  }
+}
+
+resource "aws_route_table" "default" {
+  vpc_id = "${aws_vpc.pulsar_vpc.id}"
+
+  tags {
+    Name = "Pulsar-Route-Table"
+  }
+}
+
+resource "aws_route" "default" {
+  route_table_id         = "${aws_route_table.default.id}"
+  destination_cidr_block = "0.0.0.0/0"
+  nat_gateway_id         = "${aws_nat_gateway.default.id}"
+}
+
+resource "aws_route_table_association" "default" {
+  subnet_id      = "${aws_subnet.default.id}"
+  route_table_id = "${aws_vpc.pulsar_vpc.main_route_table_id}"
+}
+
+/* Misc */
+resource "aws_eip" "default" {
+  vpc = true
+  depends_on = ["aws_internet_gateway.default"]
+}
+
+resource "aws_internet_gateway" "default" {
+  vpc_id = "${aws_vpc.pulsar_vpc.id}"
+
+  tags {
+    Name = "Pulsar-Internet-Gateway"
+  }
+}
+
+resource "aws_nat_gateway" "default" {
+  allocation_id = "${aws_eip.default.id}"
+  subnet_id     = "${aws_subnet.default.id}"
+  depends_on    = ["aws_internet_gateway.default"]
+
+  tags {
+    Name = "Pulsar-NAT-Gateway"
+  }
+}
+
+/* Public internet route */
+resource "aws_route" "internet_access" {
+  route_table_id         = "${aws_vpc.pulsar_vpc.main_route_table_id}"
+  destination_cidr_block = "0.0.0.0/0"
+  gateway_id             = "${aws_internet_gateway.default.id}"
+}
+
+/* Load balancer */
+resource "aws_elb" "default" {
+  name               = "pulsar-elb"
+  instances          = ["${aws_instance.pulsar.*.id}"]
+  security_groups    = ["${aws_security_group.elb.id}"]
+  subnets            = ["${aws_subnet.default.id}"]
+
+  listener {
+    instance_port     = 8080
+    instance_protocol = "http"
+    lb_port           = 8080
+    lb_protocol       = "http"
+  }
+
+  listener {
+    instance_port     = 6650
+    instance_protocol = "tcp"
+    lb_port           = 6650
+    lb_protocol       = "tcp"
+  }
+
+  cross_zone_load_balancing = false
+
+  tags {
+    Name = "Pulsar-Load-Balancer"
+  }
+}
\ No newline at end of file
diff --git a/deployment/terraform-ansible/aws/output.tf b/deployment/terraform-ansible/aws/output.tf
new file mode 100644
index 000000000..830992b9a
--- /dev/null
+++ b/deployment/terraform-ansible/aws/output.tf
@@ -0,0 +1,15 @@
+output "dns_name" {
+  value = "${aws_elb.default.dns_name}"
+}
+
+output "pulsar_service_url" {
+  value = "pulsar://${aws_elb.default.dns_name}:6650"
+}
+
+output "pulsar_web_url" {
+  value = "http://${aws_elb.default.dns_name}:8080"
+}
+
+output "pulsar_ssh_host" {
+  value = "${aws_instance.pulsar.0.public_ip}"
+}
\ No newline at end of file
diff --git a/deployment/terraform-ansible/aws/provider.tf b/deployment/terraform-ansible/aws/provider.tf
new file mode 100644
index 000000000..35298f541
--- /dev/null
+++ b/deployment/terraform-ansible/aws/provider.tf
@@ -0,0 +1,4 @@
+provider "aws" {
+  region  = "${var.region}"
+  version = "1.5"
+}
diff --git a/deployment/terraform-ansible/aws/security.tf b/deployment/terraform-ansible/aws/security.tf
new file mode 100644
index 000000000..c02166c35
--- /dev/null
+++ b/deployment/terraform-ansible/aws/security.tf
@@ -0,0 +1,58 @@
+resource "aws_security_group" "elb" {
+  name   = "pulsar-elb"
+  vpc_id = "${aws_vpc.pulsar_vpc.id}"
+
+  ingress {
+    from_port   = 6650
+    to_port     = 6650
+    protocol    = "tcp"
+    cidr_blocks = ["0.0.0.0/0"]
+  }
+
+  ingress {
+    from_port   = 8080
+    to_port     = 8080
+    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"]
+  }
+}
+
+resource "aws_security_group" "default" {
+  name   = "pulsar-terraform"
+  vpc_id = "${aws_vpc.pulsar_vpc.id}"
+
+  # SSH access from anywhere
+  ingress {
+    from_port   = 22
+    to_port     = 22
+    protocol    = "tcp"
+    cidr_blocks = ["0.0.0.0/0"]
+  }
+
+  # All ports open within the VPC
+  ingress {
+    from_port   = 0
+    to_port     = 65535
+    protocol    = "tcp"
+    cidr_blocks = ["${var.base_cidr_block}"]
+  }
+
+  # outbound internet access
+  egress {
+    from_port   = 0
+    to_port     = 0
+    protocol    = "-1"
+    cidr_blocks = ["0.0.0.0/0"]
+  }
+
+  tags {
+    Name = "Pulsar-Security-Group"
+  }
+}
\ No newline at end of file
diff --git a/deployment/terraform-ansible/aws/variables.tf b/deployment/terraform-ansible/aws/variables.tf
new file mode 100644
index 000000000..b3e5b0d03
--- /dev/null
+++ b/deployment/terraform-ansible/aws/variables.tf
@@ -0,0 +1,43 @@
+variable "public_key_path" {
+  description = <<DESCRIPTION
+Path to the SSH public key to be used for authentication.
+Ensure this keypair is added to your local SSH agent so provisioners can
+connect.
+
+Example: ~/.ssh/my_keys.pub
+Default: ~/.ssh/id_rsa.pub
+DESCRIPTION
+}
+
+variable "key_name_prefix" {
+  description = "The prefix for the randomly generated name for the AWS key pair to be used for SSH connections (e.g. `pulsar-terraform-ssh-keys-0a1b2cd3`)"
+  default     = "pulsar-terraform-ssh-keys"
+}
+
+variable "region" {
+  description = "The AWS region in which the Pulsar cluster will be deployed"
+}
+
+variable "availability_zone" {
+  description = "The AWS availability zone in which the cluster will run"
+}
+
+variable "aws_ami" {
+  description = "The AWS AMI to be used by the Pulsar cluster"
+}
+
+variable "num_zookeeper_nodes" {
+  description = "The number of EC2 instances running ZooKeeper"
+}
+
+variable "num_pulsar_brokers" {
+  description = "The number of EC2 instances running a Pulsar broker plus a BookKeeper bookie"
+}
+
+variable "instance_types" {
+  type = "map"
+}
+
+variable "base_cidr_block" {
+  description = "The baseline CIDR block to be used by network assets for the Pulsar cluster"
+}
\ No newline at end of file
diff --git a/deployment/terraform-ansible/deploy-pulsar.yaml b/deployment/terraform-ansible/deploy-pulsar.yaml
new file mode 100644
index 000000000..9129d3846
--- /dev/null
+++ b/deployment/terraform-ansible/deploy-pulsar.yaml
@@ -0,0 +1,182 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+- name: Disk setup
+  hosts: pulsar
+  connection: ssh
+  become: true
+  tasks:
+    - command: >
+        tuned-adm profile latency-performance
+    - name: Create and mount disks
+      mount:
+        path: "{{ item.path }}"
+        src: "{{ item.src }}"
+        fstype: xfs
+        opts: defaults,noatime,nodiscard
+        state: present
+      with_items:
+        - { path: "/mnt/journal", src: "/dev/nvme0n1" }
+        - { path: "/mnt/storage", src: "/dev/nvme1n1" }
+
+- name: Pulsar setup
+  hosts: all
+  connection: ssh
+  become: true
+  tasks:
+    - name: Create necessary directories
+      file:
+        path: "{{ item }}"
+        state: directory
+      with_items: ["/opt/pulsar"]
+    - name: Install RPM packages
+      yum: pkg={{ item }} state=latest
+      with_items:
+        - wget
+        - java
+        - sysstat
+        - vim
+    - set_fact:
+        zookeeper_servers: "{{ groups['zookeeper']|map('extract', hostvars, ['ansible_default_ipv4', 'address'])|map('regex_replace', '(.*)', '\\1:2181') | join(',') }}"
+        service_url: "pulsar://{{ hostvars[groups['pulsar'][0]].public_ip }}:6650/"
+        http_url: "http://{{ hostvars[groups['pulsar'][0]].public_ip }}:8080/"
+        pulsar_version: "1.20.0-incubating"
+
+    - name: Download Pulsar binary package
+      unarchive:
+        src: http://archive.apache.org/dist/incubator/pulsar/pulsar-{{ pulsar_version }}/apache-pulsar-{{ pulsar_version }}-bin.tar.gz
+        remote_src: yes
+        dest: /opt/pulsar
+        extra_opts: ["--strip-components=1"]
+    - set_fact:
+        max_heap_memory: "24g"
+        max_direct_memory: "24g"
+    - name: Add pulsar_env.sh configuration file
+      template:
+        src: "../templates/pulsar_env.sh"
+        dest: "/opt/pulsar/conf/pulsar_env.sh"
+
+- name: Set up ZooKeeper
+  hosts: zookeeper
+  connection: ssh
+  become: true
+  tasks:
+    - set_fact:
+        zid: "{{ groups['zookeeper'].index(inventory_hostname) }}"
+        max_heap_memory: "512m"
+        max_direct_memory: "512m"
+        cluster_name: "local"
+    - name: Create ZooKeeper data directory
+      file:
+        path: "/opt/pulsar/{{ item }}"
+        state: directory
+      with_items:
+        - data/zookeeper
+    - name: Add pulsar_env.sh configuration file 
+      template:
+        src: "../templates/pulsar_env.sh"
+        dest: "/opt/pulsar/conf/pulsar_env.sh"
+    - name: Add zookeeper.conf file
+      template:
+        src: "../templates/zoo.cfg"
+        dest: "/opt/pulsar/conf/zookeeper.conf"
+    - name: Add myid file for ZooKeeper
+      template:
+        src: "../templates/myid"
+        dest: "/opt/pulsar/data/zookeeper/myid"
+    - name: Add zookeeper.service systemd file
+      template:
+        src: "../templates/zookeeper.service"
+        dest: "/etc/systemd/system/zookeeper.service"
+    - name: systemd ZooKeeper start
+      systemd:
+        state: restarted
+        daemon_reload: yes
+        name: "zookeeper"
+    - name: Initialize cluster metadata
+      shell: |
+        bin/pulsar initialize-cluster-metadata \
+          --cluster {{ cluster_name }} \
+          --zookeeper localhost:2181 \
+          --global-zookeeper localhost:2181 \
+          --web-service-url {{ http_url }} \
+          --broker-service-url {{ service_url }}
+      args:
+        chdir: /opt/pulsar
+      when: groups['zookeeper'][0] == inventory_hostname
+
+- name: Set up Bookkeeper
+  hosts: pulsar
+  connection: ssh
+  become: true
+  tasks:
+    - template:
+        src: "../templates/bookkeeper.conf"
+        dest: "/opt/pulsar/conf/bookkeeper.conf"
+    - name: Install bookkeeper systemd service
+      template:
+        src: "../templates/bookkeeper.service"
+        dest: "/etc/systemd/system/bookkeeper.service"
+    - systemd:
+        state: restarted
+        daemon_reload: yes
+        name: "bookkeeper"
+
+- name: Set up Pulsar
+  hosts: pulsar
+  connection: ssh
+  become: true
+  tasks:
+    - name: Set up broker
+      template:
+        src: "../templates/broker.conf"
+        dest: "/opt/pulsar/conf/broker.conf"
+    - template:
+        src: "../templates/pulsar.service"
+        dest: "/etc/systemd/system/pulsar.service"
+    - systemd:
+        state: restarted
+        daemon_reload: yes
+        name: "pulsar"
+
+- name: Pulsar multi-tenancy setup
+  hosts: pulsar
+  connection: ssh
+  become: true
+  tasks:
+    - name: Create default property and namespace
+      shell: |
+        bin/pulsar-admin properties create public \
+          --allowed-clusters local \
+          --admin-roles all
+        bin/pulsar-admin namespaces create public/local/default
+      args:
+        chdir: /opt/pulsar
+      when: groups['zookeeper'][0] == inventory_hostname
+      
+- name:  Hosts addresses
+  hosts: localhost
+  become: false
+  tasks:
+    - debug:
+        msg: "Zookeeper servers {{ item }}"
+      with_items: "{{ groups['zookeeper'] }}"
+    - debug:
+        msg: "Pulsar/BookKeeper servers {{ item }}"
+      with_items: "{{ groups['pulsar'] }}"
diff --git a/deployment/terraform-ansible/templates/bookkeeper.conf b/deployment/terraform-ansible/templates/bookkeeper.conf
new file mode 100644
index 000000000..a3ede9389
--- /dev/null
+++ b/deployment/terraform-ansible/templates/bookkeeper.conf
@@ -0,0 +1,355 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+zkServers={{ zookeeper_servers }}
+
+advertisedAddress={{ hostvars[inventory_hostname].private_ip }}
+
+# Use multiple journals to better exploit SSD throughput
+journalDirectories=/mnt/journal/1,/mnt/journal/2,/mnt/journal/3,/mnt/journal/4
+ledgerDirectories=/mnt/storage
+
+dbStorage_writeCacheMaxSizeMb=4096
+dbStorage_readAheadCacheMaxSizeMb=4096
+dbStorage_rocksDB_blockCacheSize=4294967296
+
+
+## Regular Bookie settings
+
+# Port that bookie server listen on
+bookiePort=3181
+
+# Set the network interface that the bookie should listen on.
+# If not set, the bookie will listen on all interfaces.
+#listeningInterface=eth0
+
+# Whether the bookie allowed to use a loopback interface as its primary
+# interface(i.e. the interface it uses to establish its identity)?
+# By default, loopback interfaces are not allowed as the primary
+# interface.
+# Using a loopback interface as the primary interface usually indicates
+# a configuration error. For example, its fairly common in some VPS setups
+# to not configure a hostname, or to have the hostname resolve to
+# 127.0.0.1. If this is the case, then all bookies in the cluster will
+# establish their identities as 127.0.0.1:3181, and only one will be able
+# to join the cluster. For VPSs configured like this, you should explicitly
+# set the listening interface.
+allowLoopback=false
+
+# Configure a specific hostname or IP address that the bookie should use to advertise itself to
+# clients. If not set, bookie will advertised its own IP address or hostname, depending on the
+# listeningInterface and `seHostNameAsBookieID settings.
+# advertisedAddress=
+
+# Directory Bookkeeper outputs its write ahead log
+# journalDirectory=data/bookkeeper/journal
+
+# Directory Bookkeeper outputs ledger snapshots
+# could define multi directories to store snapshots, separated by ','
+# For example:
+# ledgerDirectories=/tmp/bk1-data,/tmp/bk2-data
+#
+# Ideally ledger dirs and journal dir are each in a differet device,
+# which reduce the contention between random i/o and sequential write.
+# It is possible to run with a single disk, but performance will be significantly lower.
+# ledgerDirectories=data/bookkeeper/ledgers
+# Directories to store index files. If not specified, will use ledgerDirectories to store.
+# indexDirectories=data/bookkeeper/ledgers
+
+# Ledger Manager Class
+# What kind of ledger manager is used to manage how ledgers are stored, managed
+# and garbage collected. Try to read 'BookKeeper Internals' for detail info.
+ledgerManagerType=hierarchical
+
+# Root zookeeper path to store ledger metadata
+# This parameter is used by zookeeper-based ledger manager as a root znode to
+# store all ledgers.
+zkLedgersRootPath=/ledgers
+
+# Ledger storage implementation class
+ledgerStorageClass=org.apache.bookkeeper.bookie.storage.ldb.DbLedgerStorage
+
+# Enable/Disable entry logger preallocation
+entryLogFilePreallocationEnabled=true
+
+# Max file size of entry logger, in bytes
+# A new entry log file will be created when the old one reaches the file size limitation
+logSizeLimit=2147483648
+
+# Threshold of minor compaction
+# For those entry log files whose remaining size percentage reaches below
+# this threshold will be compacted in a minor compaction.
+# If it is set to less than zero, the minor compaction is disabled.
+minorCompactionThreshold=0.2
+
+# Interval to run minor compaction, in seconds
+# If it is set to less than zero, the minor compaction is disabled.
+minorCompactionInterval=3600
+
+# Threshold of major compaction
+# For those entry log files whose remaining size percentage reaches below
+# this threshold will be compacted in a major compaction.
+# Those entry log files whose remaining size percentage is still
+# higher than the threshold will never be compacted.
+# If it is set to less than zero, the minor compaction is disabled.
+majorCompactionThreshold=0.5
+
+# Interval to run major compaction, in seconds
+# If it is set to less than zero, the major compaction is disabled.
+majorCompactionInterval=86400
+
+# Set the maximum number of entries which can be compacted without flushing.
+# When compacting, the entries are written to the entrylog and the new offsets
+# are cached in memory. Once the entrylog is flushed the index is updated with
+# the new offsets. This parameter controls the number of entries added to the
+# entrylog before a flush is forced. A higher value for this parameter means
+# more memory will be used for offsets. Each offset consists of 3 longs.
+# This parameter should _not_ be modified unless you know what you're doing.
+# The default is 100,000.
+compactionMaxOutstandingRequests=100000
+
+# Set the rate at which compaction will readd entries. The unit is adds per second.
+compactionRate=1000
+
+# Throttle compaction by bytes or by entries.
+isThrottleByBytes=false
+
+# Set the rate at which compaction will readd entries. The unit is adds per second.
+compactionRateByEntries=1000
+
+# Set the rate at which compaction will readd entries. The unit is bytes added per second.
+compactionRateByBytes=1000000
+
+# Max file size of journal file, in mega bytes
+# A new journal file will be created when the old one reaches the file size limitation
+#
+journalMaxSizeMB=2048
+
+# Max number of old journal file to kept
+# Keep a number of old journal files would help data recovery in specia case
+#
+journalMaxBackups=5
+
+# How much space should we pre-allocate at a time in the journal
+journalPreAllocSizeMB=16
+
+# Size of the write buffers used for the journal
+journalWriteBufferSizeKB=64
+
+# Should we remove pages from page cache after force write
+journalRemoveFromPageCache=true
+
+# Should we group journal force writes, which optimize group commit
+# for higher throughput
+journalAdaptiveGroupWrites=true
+
+# Maximum latency to impose on a journal write to achieve grouping
+journalMaxGroupWaitMSec=1
+
+# All the journal writes and commits should be aligned to given size
+journalAlignmentSize=4096
+
+# Maximum writes to buffer to achieve grouping
+journalBufferedWritesThreshold=524288
+
+# If we should flush the journal when journal queue is empty
+journalFlushWhenQueueEmpty=false
+
+# The number of threads that should handle journal callbacks
+numJournalCallbackThreads=8
+
+# The number of max entries to keep in fragment for re-replication
+rereplicationEntryBatchSize=5000
+
+# How long the interval to trigger next garbage collection, in milliseconds
+# Since garbage collection is running in background, too frequent gc
+# will heart performance. It is better to give a higher number of gc
+# interval if there is enough disk capacity.
+gcWaitTime=900000
+
+# How long the interval to trigger next garbage collection of overreplicated
+# ledgers, in milliseconds [Default: 1 day]. This should not be run very frequently since we read
+# the metadata for all the ledgers on the bookie from zk
+gcOverreplicatedLedgerWaitTime=86400000
+
+# How long the interval to flush ledger index pages to disk, in milliseconds
+# Flushing index files will introduce much random disk I/O.
+# If separating journal dir and ledger dirs each on different devices,
+# flushing would not affect performance. But if putting journal dir
+# and ledger dirs on same device, performance degrade significantly
+# on too frequent flushing. You can consider increment flush interval
+# to get better performance, but you need to pay more time on bookie
+# server restart after failure.
+#
+flushInterval=60000
+
+# Interval to watch whether bookie is dead or not, in milliseconds
+#
+bookieDeathWatchInterval=1000
+
+## zookeeper client settings
+
+# A list of one of more servers on which zookeeper is running.
+# The server list can be comma separated values, for example:
+# zkServers=zk1:2181,zk2:2181,zk3:2181
+zkServers=localhost:2181
+# ZooKeeper client session timeout in milliseconds
+# Bookie server will exit if it received SESSION_EXPIRED because it
+# was partitioned off from ZooKeeper for more than the session timeout
+# JVM garbage collection, disk I/O will cause SESSION_EXPIRED.
+# Increment this value could help avoiding this issue
+zkTimeout=30000
+
+## NIO Server settings
+
+# This settings is used to enabled/disabled Nagle's algorithm, which is a means of
+# improving the efficiency of TCP/IP networks by reducing the number of packets
+# that need to be sent over the network.
+# If you are sending many small messages, such that more than one can fit in
+# a single IP packet, setting server.tcpnodelay to false to enable Nagle algorithm
+# can provide better performance.
+# Default value is true.
+#
+serverTcpNoDelay=true
+
+## ledger cache settings
+
+# Max number of ledger index files could be opened in bookie server
+# If number of ledger index files reaches this limitation, bookie
+# server started to swap some ledgers from memory to disk.
+# Too frequent swap will affect performance. You can tune this number
+# to gain performance according your requirements.
+openFileLimit=0
+
+# Size of a index page in ledger cache, in bytes
+# A larger index page can improve performance writing page to disk,
+# which is efficent when you have small number of ledgers and these
+# ledgers have similar number of entries.
+# If you have large number of ledgers and each ledger has fewer entries,
+# smaller index page would improve memory usage.
+# pageSize=8192
+
+# How many index pages provided in ledger cache
+# If number of index pages reaches this limitation, bookie server
+# starts to swap some ledgers from memory to disk. You can increment
+# this value when you found swap became more frequent. But make sure
+# pageLimit*pageSize should not more than JVM max memory limitation,
+# otherwise you would got OutOfMemoryException.
+# In general, incrementing pageLimit, using smaller index page would
+# gain bettern performance in lager number of ledgers with fewer entries case
+# If pageLimit is -1, bookie server will use 1/3 of JVM memory to compute
+# the limitation of number of index pages.
+pageLimit=0
+
+#If all ledger directories configured are full, then support only read requests for clients.
+#If "readOnlyModeEnabled=true" then on all ledger disks full, bookie will be converted
+#to read-only mode and serve only read requests. Otherwise the bookie will be shutdown.
+#By default this will be disabled.
+readOnlyModeEnabled=true
+
+#For each ledger dir, maximum disk space which can be used.
+#Default is 0.95f. i.e. 95% of disk can be used at most after which nothing will
+#be written to that partition. If all ledger dir partions are full, then bookie
+#will turn to readonly mode if 'readOnlyModeEnabled=true' is set, else it will
+#shutdown.
+#Valid values should be in between 0 and 1 (exclusive).
+diskUsageThreshold=0.95
+
+#Disk check interval in milli seconds, interval to check the ledger dirs usage.
+#Default is 10000
+diskCheckInterval=10000
+
+# Interval at which the auditor will do a check of all ledgers in the cluster.
+# By default this runs once a week. The interval is set in seconds.
+# To disable the periodic check completely, set this to 0.
+# Note that periodic checking will put extra load on the cluster, so it should
+# not be run more frequently than once a day.
+auditorPeriodicCheckInterval=604800
+
+# The interval between auditor bookie checks.
+# The auditor bookie check, checks ledger metadata to see which bookies should
+# contain entries for each ledger. If a bookie which should contain entries is
+# unavailable, then the ledger containing that entry is marked for recovery.
+# Setting this to 0 disabled the periodic check. Bookie checks will still
+# run when a bookie fails.
+# The interval is specified in seconds.
+auditorPeriodicBookieCheckInterval=86400
+
+# number of threads that should handle write requests. if zero, the writes would
+# be handled by netty threads directly.
+numAddWorkerThreads=0
+
+# number of threads that should handle read requests. if zero, the reads would
+# be handled by netty threads directly.
+numReadWorkerThreads=8
+
+# If read workers threads are enabled, limit the number of pending requests, to
+# avoid the executor queue to grow indefinitely
+maxPendingReadRequestsPerThread=2500
+
+# The number of bytes we should use as capacity for BufferedReadChannel. Default is 512 bytes.
+readBufferSizeBytes=4096
+
+# The number of bytes used as capacity for the write buffer. Default is 64KB.
+writeBufferSizeBytes=65536
+
+# Whether the bookie should use its hostname to register with the
+# co-ordination service(eg: zookeeper service).
+# When false, bookie will use its ipaddress for the registration.
+# Defaults to false.
+useHostNameAsBookieID=false
+
+# Stats Provider Class
+statsProviderClass=org.apache.bookkeeper.stats.PrometheusMetricsProvider
+# Default port for Prometheus metrics exporter
+prometheusStatsHttpPort=8000
+
+
+## DB Ledger storage configuration
+
+# Size of Write Cache. Memory is allocated from JVM direct memory.
+# Write cache is used to buffer entries before flushing into the entry log
+# For good performance, it should be big enough to hold a sub
+# dbStorage_writeCacheMaxSizeMb=512
+
+# Size of Read cache. Memory is allocated from JVM direct memory.
+# This read cache is pre-filled doing read-ahead whenever a cache miss happens
+# dbStorage_readAheadCacheMaxSizeMb=256
+
+# How many entries to pre-fill in cache after a read cache miss
+dbStorage_readAheadCacheBatchSize=1000
+
+## RocksDB specific configurations
+## DbLedgerStorage uses RocksDB to store the indexes from
+## (ledgerId, entryId) -> (entryLog, offset)
+
+# Size of RocksDB block-cache. For best performance, this cache
+# should be big enough to hold a significant portion of the index
+# database which can reach ~2GB in some cases
+# 256 MBytes
+# dbStorage_rocksDB_blockCacheSize=268435456
+
+dbStorage_rocksDB_writeBufferSizeMB=64
+dbStorage_rocksDB_sstSizeInMB=64
+dbStorage_rocksDB_blockSize=65536
+dbStorage_rocksDB_bloomFilterBitsPerKey=10
+dbStorage_rocksDB_numLevels=-1
+dbStorage_rocksDB_numFilesInLevel0=4
+dbStorage_rocksDB_maxSizeInLevel1MB=256
diff --git a/deployment/terraform-ansible/templates/bookkeeper.service b/deployment/terraform-ansible/templates/bookkeeper.service
new file mode 100644
index 000000000..1056ad809
--- /dev/null
+++ b/deployment/terraform-ansible/templates/bookkeeper.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Bookkeeper
+After=network.target
+
+[Service]
+ExecStart=/opt/pulsar/bin/pulsar bookie
+WorkingDirectory=/opt/pulsar
+RestartSec=1s
+Restart=on-failure
+Type=simple
+
+[Install]
+WantedBy=multi-user.target
diff --git a/deployment/terraform-ansible/templates/broker.conf b/deployment/terraform-ansible/templates/broker.conf
new file mode 100644
index 000000000..de7836670
--- /dev/null
+++ b/deployment/terraform-ansible/templates/broker.conf
@@ -0,0 +1,34 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+
+### Use all the broker defaults except for the following
+
+# Zookeeper quorum connection string
+zookeeperServers={{ zookeeper_servers }}
+
+# Global Zookeeper quorum connection string
+globalZookeeperServers={{ zookeeper_servers }}
+
+# Hostname or IP address the service advertises to the outside world. If not set, the value of InetAddress.getLocalHost().getHostName() is used.
+advertisedAddress={{ hostvars[inventory_hostname].public_ip }}
+
+# Name of the cluster to which this broker belongs to
+clusterName=local
diff --git a/deployment/terraform-ansible/templates/client.conf b/deployment/terraform-ansible/templates/client.conf
new file mode 100644
index 000000000..81c65772a
--- /dev/null
+++ b/deployment/terraform-ansible/templates/client.conf
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+# Pulsar Client configuration
+webServiceUrl=http://{{ hostvars[groups['pulsar'][0]].private_ip }}:8080/
+
+brokerServiceUrl=pulsar://{{ hostvars[groups['pulsar'][0]].private_ip }}:6650/
diff --git a/deployment/terraform-ansible/templates/myid b/deployment/terraform-ansible/templates/myid
new file mode 100644
index 000000000..6b8ddbb2c
--- /dev/null
+++ b/deployment/terraform-ansible/templates/myid
@@ -0,0 +1 @@
+{{ zid }}
diff --git a/deployment/terraform-ansible/templates/pulsar.service b/deployment/terraform-ansible/templates/pulsar.service
new file mode 100644
index 000000000..15886c1ca
--- /dev/null
+++ b/deployment/terraform-ansible/templates/pulsar.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Pulsar Broker
+After=network.target
+
+[Service]
+ExecStart=/opt/pulsar/bin/pulsar broker
+WorkingDirectory=/opt/pulsar
+RestartSec=1s
+Restart=on-failure
+Type=simple
+
+[Install]
+WantedBy=multi-user.target
diff --git a/deployment/terraform-ansible/templates/pulsar_env.sh b/deployment/terraform-ansible/templates/pulsar_env.sh
new file mode 100644
index 000000000..d6fb45fdd
--- /dev/null
+++ b/deployment/terraform-ansible/templates/pulsar_env.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Set JAVA_HOME here to override the environment setting
+# JAVA_HOME=
+
+# default settings for starting pulsar broker
+
+# Log4j configuration file
+# PULSAR_LOG_CONF=
+
+# Logs location
+# PULSAR_LOG_DIR=
+
+# Configuration file of settings used in broker server
+# PULSAR_BROKER_CONF=
+
+# Configuration file of settings used in bookie server
+# PULSAR_BOOKKEEPER_CONF=
+
+# Configuration file of settings used in zookeeper server
+# PULSAR_ZK_CONF=
+
+# Configuration file of settings used in global zookeeper server
+# PULSAR_GLOBAL_ZK_CONF=
+
+# Extra options to be passed to the jvm
+PULSAR_MEM=" -Xms{{ max_heap_memory }} -Xmx{{ max_heap_memory }} -XX:MaxDirectMemorySize={{ max_direct_memory }}"
+
+# Garbage collection options
+# PULSAR_GC=" -XX:+UseG1GC -XX:MaxGCPauseMillis=10 -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -XX:ParallelGCThreads=32 -XX:ConcGCThreads=32 -XX:G1NewSizePercent=50 -XX:+DisableExplicitGC -XX:-ResizePLAB"
+PULSAR_GC=" -XX:+UseShenandoahGC -XX:+ParallelRefProcEnabled -XX:+UnlockExperimentalVMOptions -XX:+AggressiveOpts -XX:+DoEscapeAnalysis "
+PULSAR_GC="${PULSAR_GC} -XX:+PerfDisableSharedMem -XX:+AlwaysPreTouch -XX:-UseBiasedLocking"
+PULSAR_GC="${PULSAR_GC} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m -Xloggc:/dev/shm/gc_%p.log"
+
+# Extra options to be passed to the jvm
+PULSAR_EXTRA_OPTS="${PULSAR_EXTRA_OPTS} ${PULSAR_MEM} ${PULSAR_GC} -Dio.netty.leakDetectionLevel=disabled -Dio.netty.recycler.maxCapacity.default=1000 -Dio.netty.recycler.linkCapacity=1024"
+
+# Add extra paths to the bookkeeper classpath
+# PULSAR_EXTRA_CLASSPATH=
+
+#Folder where the Bookie server PID file should be stored
+#PULSAR_PID_DIR=
+
+#Wait time before forcefully kill the pulser server instance, if the stop is not successful
+#PULSAR_STOP_TIMEOUT=
diff --git a/deployment/terraform-ansible/templates/zoo.cfg b/deployment/terraform-ansible/templates/zoo.cfg
new file mode 100644
index 000000000..b33cad819
--- /dev/null
+++ b/deployment/terraform-ansible/templates/zoo.cfg
@@ -0,0 +1,49 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# The number of milliseconds of each tick
+tickTime=2000
+# The number of ticks that the initial
+# synchronization phase can take
+initLimit=10
+# The number of ticks that can pass between
+# sending a request and getting an acknowledgement
+syncLimit=5
+# the directory where the snapshot is stored.
+dataDir=data/zookeeper
+# the port at which the clients will connect
+clientPort=2181
+# the maximum number of client connections.
+# increase this if you need to handle more clients
+#maxClientCnxns=60
+#
+# Be sure to read the maintenance section of the
+# administrator guide before turning on autopurge.
+#
+# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
+#
+# The number of snapshots to retain in dataDir
+autopurge.snapRetainCount=3
+# Purge task interval in hours
+# Set to "0" to disable auto purge feature
+autopurge.purgeInterval=1
+
+{% for zk in groups['zookeeper'] %}
+server.{{ hostvars[zk].zid }}={{ hostvars[zk].private_ip }}:2888:3888
+{% endfor %}
diff --git a/deployment/terraform-ansible/templates/zookeeper.service b/deployment/terraform-ansible/templates/zookeeper.service
new file mode 100644
index 000000000..9c9f6e67b
--- /dev/null
+++ b/deployment/terraform-ansible/templates/zookeeper.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=ZooKeeper Local
+After=network.target
+
+[Service]
+Environment=OPTS=-Dstats_server_port=2182
+ExecStart=/opt/pulsar/bin/pulsar zookeeper
+WorkingDirectory=/opt/pulsar
+RestartSec=1s
+Restart=on-failure
+Type=simple
+
+[Install]
+WantedBy=multi-user.target
diff --git a/deployment/terraform-ansible/terraform.tfstate b/deployment/terraform-ansible/terraform.tfstate
new file mode 100644
index 000000000..8539234f0
--- /dev/null
+++ b/deployment/terraform-ansible/terraform.tfstate
@@ -0,0 +1,16 @@
+{
+    "version": 3,
+    "terraform_version": "0.11.0",
+    "serial": 1,
+    "lineage": "b5fa0907-e53e-4607-9800-610e5de4ec10",
+    "modules": [
+        {
+            "path": [
+                "root"
+            ],
+            "outputs": {},
+            "resources": {},
+            "depends_on": []
+        }
+    ]
+}
diff --git a/site/_data/sidebar.yaml b/site/_data/sidebar.yaml
index d54e94a1d..54ce5bc89 100644
--- a/site/_data/sidebar.yaml
+++ b/site/_data/sidebar.yaml
@@ -33,6 +33,8 @@ groups:
 - title: Deployment
   dir: deployment
   docs:
+  - title: Deploy on Amazon Web Services
+    endpoint: aws-cluster
   - title: Single cluster on bare metal
     endpoint: cluster
   - title: Multi-cluster instance on bare metal
diff --git a/site/_sass/_docs.scss b/site/_sass/_docs.scss
index de1751655..d00a34d16 100644
--- a/site/_sass/_docs.scss
+++ b/site/_sass/_docs.scss
@@ -50,6 +50,7 @@
 
       h1.docs-title {
         font-size: 40px;
+        line-height: 3.2rem;
       }
 
       span.docs-lead {
diff --git a/site/docs/latest/deployment/aws-cluster.md b/site/docs/latest/deployment/aws-cluster.md
new file mode 100644
index 000000000..7e9b96712
--- /dev/null
+++ b/site/docs/latest/deployment/aws-cluster.md
@@ -0,0 +1,182 @@
+---
+title: Deploying a Pulsar cluster on AWS using Terraform and Ansible
+tags: [admin, deployment, cluster, ansible, terraform]
+---
+
+{% include admonition.html type="info"
+   content="For instructions on deploying a single Pulsar cluster manually rather than using Terraform and Ansible, see [Deploying a Pulsar cluster on bare metal](../cluster). For instructions on manually deploying a multi-cluster Pulsar instance, see [Deploying a Pulsar instance on bare metal](../instance)." %}
+
+One of the easiest ways to get a Pulsar {% popover cluster %} running on [Amazon Web Services](https://aws.amazon.com/) (AWS) is to use the the [Terraform](https://terraform.io) infrastructure provisioning tool and the [Ansible](https://www.ansible.com) server automation tool. Terraform can create the resources necessary to run the Pulsar cluster---[EC2](https://aws.amazon.com/ec2/) instances, networking and security infrastructure, etc.---while Ansible can install and run Pulsar on the provisioned resources.
+
+## Requirements and setup
+
+In order install a Pulsar cluster on AWS using Terraform and Ansible, you'll need:
+
+* An [AWS account](https://aws.amazon.com/account/) and the [`aws`](https://aws.amazon.com/cli/) command-line tool
+* Python and [pip](https://pip.pypa.io/en/stable/)
+* The [`terraform-inventory`](https://github.com/adammck/terraform-inventory) tool, which enables Ansible to use Terraform artifacts
+
+You'll also need to make sure that you're currently logged into your AWS account via the `aws` tool:
+
+```bash
+$ aws configure
+```
+
+## Installation
+
+You can install Ansible on Linux or macOS using pip.
+
+```bash
+$ pip install ansible
+```
+
+You can install Terraform using the instructions [here](https://www.terraform.io/intro/getting-started/install.html).
+
+You'll also need to have the Terraform and Ansible configurations for Pulsar locally on your machine. They're contained in Pulsar's [GitHub repository](https://github.com/apache/incubator-pulsar), which you can fetch using Git:
+
+```bash
+$ git clone https://github.com/apache/incubator-pulsar
+$ cd incubator-pulsar/deployment/terraform-ansible/aws
+```
+
+## SSH setup
+
+In order to create the necessary AWS resources using Terraform, you'll need to create an SSH key. To create a private SSH key in `~/.ssh/id_rsa` and a public key in `~/.ssh/id_rsa.pub`:
+
+```bash
+$ ssh-keygen -t rsa
+```
+
+Do *not* enter a passphrase (hit **Enter** when prompted instead). To verify that a key has been created:
+
+```bash
+$ ls ~/.ssh
+id_rsa               id_rsa.pub
+```
+
+## Creating AWS resources using Terraform
+
+To get started building AWS resources with Terraform, you'll need to install all Terraform dependencies:
+
+```bash
+$ terraform init
+# This will create a .terraform folder
+```
+
+Once you've done that, you can apply the default Terraform configuration:
+
+```bash
+$ terraform apply
+```
+
+You should then see this prompt:
+
+```bash
+Do you want to perform these actions?
+  Terraform will perform the actions described above.
+  Only 'yes' will be accepted to approve.
+
+  Enter a value:
+```
+
+Type `yes` and hit **Enter**. Applying the configuration could take several minutes. When it's finished, you should see `Apply complete!` along with some other information, including the number of resources created.
+
+### Applying a non-default configuration
+
+You can apply a non-default Terraform configuration by changing the values in the `terraform.tfvars` file. The following variables are available:
+
+Variable name | Description | Default
+:-------------|:------------|:-------
+`public_key_path` | The path of the public key that you've generated. | `~/.ssh/id_rsa.pub`
+`region` | The AWS region in which the Pulsar cluster will run | `us-west-2`
+`availability_zone` | The AWS availability zone in which the Pulsar cluster will run | `us-west-2a`
+`ami` | The [Amazon Machine Image](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html) (AMI) that will be used by the cluster | `ami-9fa343e7`
+`num_zookeeper_nodes` | The number of [ZooKeeper](https://zookeeper.apache.org) nodes in the ZooKeeper cluster | 3
+`num_pulsar_brokers` | The number of Pulsar brokers and BookKeeper bookies that will run in the cluster | 3
+`base_cidr_block` | The root [CIDR](http://searchnetworking.techtarget.com/definition/CIDR) that will be used by network assets for the cluster | `10.0.0.0/16`
+`instance_types` | The EC2 instance types to be used. This variable is a map with two keys: `zookeeper` for the ZooKeeper instances and `pulsar` for the Pulsar brokers and BookKeeper bookies | `t2.small` (ZooKeeper) and `i3.3xlarge` (Pulsar/BookKeeper)
+
+### What is installed
+
+When you run the Ansible playbook, the following AWS resources will be used:
+
+* 6 total [Elastic Compute Cloud](https://aws.amazon.com/ec2) (EC2) instances running the [ami-9fa343e7](https://access.redhat.com/articles/3135091) Amazon Machine Image (AMI), which runs [Red Hat Enterprise Linux (RHEL) 7.4](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html-single/7.4_release_notes/index). By default, that includes:
+  * 3 small VMs for ZooKeeper ([t2.small](https://www.ec2instances.info/?selected=t2.small) instances)
+  * 3 larger VMs for Pulsar {% popover brokers %} and BookKeeper {% popover bookies %} ([i3.4xlarge](https://www.ec2instances.info/?selected=i3.4xlarge) instances)
+* An EC2 [security group](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html)
+* A [virtual private cloud](https://aws.amazon.com/vpc/) (VPC) for security
+* An [API Gateway](https://aws.amazon.com/api-gateway/) for connections from the outside world
+* A [route table](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Route_Tables.html) for the Pulsar cluster's VPC
+* A [subnet](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html) for the VPC
+
+All EC2 instances for the cluster will run in the [us-west-2](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html) region.
+
+### Fetching your Pulsar connection URL
+
+When you apply the Terraform configuration by running `terraform apply`, Terraform will output a value for the `pulsar_service_url`. It should look something like this:
+
+```
+pulsar://pulsar-elb-1800761694.us-west-2.elb.amazonaws.com:6650
+```
+
+You can fetch that value at any time by running `terraform output pulsar_service_url` or parsing the `terraform.tstate` file (which is JSON, even though the filename doesn't reflect that):
+
+```bash
+$ cat terraform.tfstate | jq '.modules | .[0].outputs.pulsar_connection_url.value'
+```
+
+### Destroying your cluster
+
+At any point, you can destroy all AWS resources associated with your cluster using Terraform's `destroy` command:
+
+```bash
+$ terraform destroy
+```
+
+## Running the Pulsar playbook
+
+Once you've created the necessary AWS resources using Terraform, you can install and run Pulsar on the Terraform-created EC2 instances using Ansible. To do so, use this command:
+
+```bash
+$ ansible-playbook \
+  --inventory=`which terraform-inventory` \
+  ../deploy-pulsar.yaml
+```
+
+If you've created a private SSH key at a location different from `~/.ssh/id_rsa`, you can specify the different location using the `--private-key` flag:
+
+```bash
+$ ansible-playbook \
+  --inventory=`which terraform-inventory` \
+  --private-key="~/.ssh/some-non-default-key" \
+  ../deploy-pulsar.yaml
+```
+
+## Accessing the cluster
+
+You can now access your running Pulsar using the unique Pulsar connection URL for your cluster, which you can obtain using the instructions [above](#fetching-your-pulsar-connection-url).
+
+For a quick demonstration of accessing the cluster, we can use the Python client for Pulsar and the Python shell. First, install the Pulsar Python module using pip:
+
+```bash
+$ pip install pulsar-client
+```
+
+Now, open up the Python shell using the `python` command:
+
+```bash
+$ python
+```
+
+Once in the shell, run the following:
+
+```python
+>>> import pulsar
+>>> client = pulsar.Client('pulsar://pulsar-elb-1800761694.us-west-2.elb.amazonaws.com:6650')
+# Make sure to use your connection URL
+>>> producer = client.create_producer('persistent://sample/local/ns1/test-topic')
+>>> producer.send('Hello world')
+>>> client.close()
+```
+
+If all of these commands are successful, your cluster can now be used by Pulsar clients!


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services