From 3ed91c026a57ea55acb2fb728b4b27f736936cdc Mon Sep 17 00:00:00 2001 From: Luc Perkins Date: Tue, 2 Jan 2018 00:49:10 -0800 Subject: [PATCH] Provide an Ansible playbook for AWS with documentation (#920) --- .gitignore | 6 + deployment/terraform-ansible/aws/ansible.cfg | 9 + deployment/terraform-ansible/aws/instances.tf | 27 ++ deployment/terraform-ansible/aws/keys.tf | 9 + deployment/terraform-ansible/aws/network.tf | 98 +++++ deployment/terraform-ansible/aws/output.tf | 15 + deployment/terraform-ansible/aws/provider.tf | 4 + deployment/terraform-ansible/aws/security.tf | 58 +++ deployment/terraform-ansible/aws/variables.tf | 43 +++ .../terraform-ansible/deploy-pulsar.yaml | 182 +++++++++ .../templates/bookkeeper.conf | 355 ++++++++++++++++++ .../templates/bookkeeper.service | 13 + .../terraform-ansible/templates/broker.conf | 34 ++ .../terraform-ansible/templates/client.conf | 24 ++ deployment/terraform-ansible/templates/myid | 1 + .../templates/pulsar.service | 13 + .../terraform-ansible/templates/pulsar_env.sh | 63 ++++ .../terraform-ansible/templates/zoo.cfg | 49 +++ .../templates/zookeeper.service | 14 + .../terraform-ansible/terraform.tfstate | 16 + site/_data/sidebar.yaml | 2 + site/_sass/_docs.scss | 1 + site/docs/latest/deployment/aws-cluster.md | 182 +++++++++ 23 files changed, 1218 insertions(+) create mode 100644 deployment/terraform-ansible/aws/ansible.cfg create mode 100644 deployment/terraform-ansible/aws/instances.tf create mode 100644 deployment/terraform-ansible/aws/keys.tf create mode 100644 deployment/terraform-ansible/aws/network.tf create mode 100644 deployment/terraform-ansible/aws/output.tf create mode 100644 deployment/terraform-ansible/aws/provider.tf create mode 100644 deployment/terraform-ansible/aws/security.tf create mode 100644 deployment/terraform-ansible/aws/variables.tf create mode 100644 deployment/terraform-ansible/deploy-pulsar.yaml create mode 100644 deployment/terraform-ansible/templates/bookkeeper.conf create mode 100644 deployment/terraform-ansible/templates/bookkeeper.service create mode 100644 deployment/terraform-ansible/templates/broker.conf create mode 100644 deployment/terraform-ansible/templates/client.conf create mode 100644 deployment/terraform-ansible/templates/myid create mode 100644 deployment/terraform-ansible/templates/pulsar.service create mode 100644 deployment/terraform-ansible/templates/pulsar_env.sh create mode 100644 deployment/terraform-ansible/templates/zoo.cfg create mode 100644 deployment/terraform-ansible/templates/zookeeper.service create mode 100644 deployment/terraform-ansible/terraform.tfstate create mode 100644 site/docs/latest/deployment/aws-cluster.md diff --git a/.gitignore b/.gitignore index 7583b29f8f99d..1e194b2a7fd28 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 0000000000000..69cf91f13e067 --- /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 0000000000000..079660f3ac296 --- /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 0000000000000..808cee4515023 --- /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 0000000000000..7a97dc01a07fb --- /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 0000000000000..830992b9a327c --- /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 0000000000000..35298f5417a84 --- /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 0000000000000..c02166c351735 --- /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 0000000000000..b3e5b0d03299c --- /dev/null +++ b/deployment/terraform-ansible/aws/variables.tf @@ -0,0 +1,43 @@ +variable "public_key_path" { + description = < + 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 0000000000000..a3ede9389fdb9 --- /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 0000000000000..1056ad809ec1a --- /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 0000000000000..de783667062d8 --- /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 0000000000000..81c65772afaef --- /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 0000000000000..6b8ddbb2cc4af --- /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 0000000000000..15886c1ca6a1b --- /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 0000000000000..d6fb45fddf1a2 --- /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 0000000000000..b33cad8193e82 --- /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 0000000000000..9c9f6e67b8c7c --- /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 0000000000000..8539234f034e4 --- /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 7159c80099521..4a543e2cb9b30 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 b4021be800f3d..2e703222dba46 100644 --- a/site/_sass/_docs.scss +++ b/site/_sass/_docs.scss @@ -60,6 +60,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 0000000000000..7e9b967126be6 --- /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!