Added sample Ansible playbook for installing SQL Server and creating a Pacemaker-managed AG.

This commit is contained in:
Arnav Singh
2017-10-12 17:21:04 -07:00
parent 18c304866f
commit bcb5cb4e23
18 changed files with 2059 additions and 0 deletions
@@ -0,0 +1,57 @@
This is a sample Ansible playbook that shows how to install SQL Server, create a Pacemaker cluster, and create an AG managed by the cluster on a set of Linux nodes.
# Roles
- `pacemaker` - This role creates a Pacemaker cluster between the hosts.
- `mssql-server` - This role installs SQL Server on the host, runs setup to set the SA password, and starts the service.
- `mssql-server-ha` - This role enables support for HA and creates a DB Mirroring endpoint.
- `mssql-server-ag-external` - This role installs the Pacemaker resource agents, creates an AG, an optional listener, and Pacemaker resources for both.
# Try
1. Put the names of the Linux nodes in the `inventory` file
1. Configure the deployment in `play.yml`
1. Create a vault file named `vault.yml` using the template at the end of this README.
```sh
ansible-vault create vault.yml
```
1. Execute the playbook
```sh
ansible-playbook ./play.yml -i ./inventory --ask-vault-pass -e 'ansible_user=username'
```
# Vault file template
```yaml
---
ansible_ssh_pass: 'some password'
ansible_sudo_pass: 'some password'
# The password for the sa user. Only used if mssql-server needs to be installed.
sa_password: 'some password'
# The password for the master key
master_key_password: 'some password'
# The SQL password for the DBM endpoint user
dbm_password: 'some password'
# The password for the DBM cert private key
dbm_cert_password: 'some password'
# The password of the user that admins the pacemaker cluster (hacluster)
pacemaker_cluster_password: 'some password'
# The SQL password for the pacemaker user
pacemaker_password: 'some password'
```
@@ -0,0 +1,4 @@
[servers]
node1
node2
node3
@@ -0,0 +1,253 @@
#!/usr/bin/python
# Copyright (c) 2017 Microsoft Corporation
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'supported_by': 'community',
'status': ['preview']
}
DOCUMENTATION = '''
---
module: mssql_ag
short_description: Add or join availability groups on a SQL Server instance
description:
- Add or join availability groups on a SQL Server instance.
version_added: "2.2"
author: Arnav Singh (@arsing)
options:
name:
description:
- The name of the availability group to add
required: true
state:
description:
- The state to set the local replica to
choices: ["all_secondaries_or_unjoined", "all_joined_to_one_primary"]
required: true
all_replicas:
description:
- A list of all the replicas of the AG
required: false
primary:
description:
- The replica that should become the primary
required: false
local_replica:
description:
- The name of the local replica
required: false
dbm_endpoint_port:
description:
- The port of the DBM endpoint
required: false
login_port:
description:
- The TDS port of the instance
required: false
default: 1433
login_name:
description:
- The name of the user to log in to the instance
required: true
login_password:
description:
- The password of the user to log in to the instance
required: true
notes:
- Requires the mssql-tools package on the remote host.
requirements:
- python >= 2.7
- mssql-tools
'''.replace('\t', ' ')
EXAMPLES = '''
# Set all replicas of AG foo to secondary
- mssql_ag:
name: foo
state: all_secondaries_or_unjoined
login_name: sa
login_password: password
# Join all replicas of AG foo to primary on the first server in the group named servers
- mssql_ag:
name: foo
state: all_joined_to_one_primary
all_replicas: "{{ groups['servers'] }}"
primary: "{{ groups['servers'][0] }}"
local_replica: "{{ inventory_hostname }}"
login_name: sa
login_password: password
'''.replace('\t', ' ')
RETURN = '''
name:
description: The name of the AG that was created or joined
returned: success
type: string
sample: foo
'''.replace('\t', ' ')
from ansible.module_utils.basic import AnsibleModule
import subprocess
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required = True),
state = dict(choices = ['all_secondaries_or_unjoined', 'all_joined_to_one_primary'], required = True),
all_replicas = dict(type = 'list', required = False),
primary = dict(required = False),
local_replica = dict(required = False),
dbm_endpoint_port = dict(required = False),
login_port = dict(required = False, default = 1433),
login_name = dict(required = True),
login_password = dict(required = True, no_log = True)
),
required_if = [
['state', 'all_joined_to_one_primary', ['all_replicas', 'primary', 'local_replica', 'dbm_endpoint_port']]
]
)
name = module.params['name']
state = module.params['state']
all_replicas = module.params['all_replicas']
primary = module.params['primary']
local_replica = module.params['local_replica']
dbm_endpoint_port = module.params['dbm_endpoint_port']
login_port = module.params['login_port']
login_name = module.params['login_name']
login_password = module.params['login_password']
if state == "all_secondaries_or_unjoined":
sqlcmd(login_port, login_name, login_password, """
IF EXISTS (
SELECT * FROM sys.availability_groups WHERE name = {0}
)
ALTER AVAILABILITY GROUP {1} SET (ROLE = SECONDARY)
;
""".format(
quoteName(name, "'"),
quoteName(name, '[')
))
elif primary == local_replica:
def replica_spec(name, endpoint_port):
return """
{0} WITH (
ENDPOINT_URL = {1},
AVAILABILITY_MODE = SYNCHRONOUS_COMMIT,
FAILOVER_MODE = EXTERNAL,
SEEDING_MODE = AUTOMATIC
)
""".format(
quoteName(name.split('.')[0], "'"),
quoteName('tcp://{0}:{1}'.format(name, endpoint_port), "'")
)
sqlcmd(login_port, login_name, login_password, """
IF NOT EXISTS (
SELECT * FROM sys.availability_groups WHERE name = {0}
)
CREATE AVAILABILITY GROUP {1}
WITH (CLUSTER_TYPE = EXTERNAL, DB_FAILOVER = ON)
FOR REPLICA ON {2}
ELSE IF NOT EXISTS (
SELECT *
FROM sys.dm_hadr_availability_replica_states ars
JOIN sys.availability_groups ag ON ars.group_id = ag.group_id
WHERE ag.name = {0} AND ars.is_local = 1 AND ars.role = 1
)
BEGIN
EXEC sp_set_session_context @key = N'external_cluster', @value = N'yes', @read_only = 1
ALTER AVAILABILITY GROUP {1} FAILOVER
END
;
ALTER AVAILABILITY GROUP {1} GRANT CREATE ANY DATABASE
;
""".format(
quoteName(name, "'"),
quoteName(name, '['),
replica_spec(primary, dbm_endpoint_port)
))
for replica in all_replicas:
if replica != primary:
sqlcmd(login_port, login_name, login_password, """
IF NOT EXISTS (
SELECT *
FROM sys.availability_replicas ar
JOIN sys.availability_groups ag ON ar.group_id = ag.group_id
WHERE ag.name = {0} AND ar.replica_server_name = {2}
)
ALTER AVAILABILITY GROUP {1}
ADD REPLICA ON {3}
;
""".format(
quoteName(name, "'"),
quoteName(name, '['),
quoteName(replica.split('.')[0], "'"),
replica_spec(replica, dbm_endpoint_port)
))
else:
sqlcmd(login_port, login_name, login_password, """
IF NOT EXISTS (
SELECT * FROM sys.availability_groups WHERE name = {0}
)
ALTER AVAILABILITY GROUP {1} JOIN WITH (CLUSTER_TYPE = EXTERNAL)
;
ALTER AVAILABILITY GROUP {1} GRANT CREATE ANY DATABASE
;
""".format(
quoteName(name, "'"),
quoteName(name, '[')
))
module.exit_json(changed = True, name = name)
def sqlcmd(login_port, login_name, login_password, command):
subprocess.check_call([
'/opt/mssql-tools/bin/sqlcmd',
'-S',
"localhost,{0}".format(login_port),
'-U',
login_name,
'-P',
login_password,
'-b',
'-Q',
command
])
def quoteName(name, quote_char):
if quote_char == '[' or quote_char == ']':
(quote_start_char, quote_end_char) = ('[', ']')
elif quote_char == "'":
(quote_start_char, quote_end_char) = ("N'", "'")
else:
raise Exception("Unsupported quote_char {0}, must be [ or ] or '".format(quote_char))
return "{0}{1}{2}".format(quote_start_char, name.replace(quote_end_char, quote_end_char + quote_end_char), quote_end_char)
if __name__ == '__main__':
main()
@@ -0,0 +1,184 @@
#!/usr/bin/python
# Copyright (c) 2017 Microsoft Corporation
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'supported_by': 'community',
'status': ['preview']
}
DOCUMENTATION = '''
---
module: mssql_ag_listener
short_description: Create an availability group listener on a SQL Server instance
description:
- Create an availability group listener on a SQL Server instance.
version_added: "2.2"
author: Arnav Singh (@arsing)
options:
name:
description:
- The name of the listener to add
required: true
ag_name:
description:
- The name of the availability group to add the listener to
required: true
ip:
description:
- The IPs for the listener to bind to
required: true
readonly_routing_replicas:
description:
- A list of all the replicas of the AG that should participate in read-only routing
required: false
login_port:
description:
- The TDS port of the instance
required: false
default: 1433
login_name:
description:
- The name of the user to log in to the instance
required: true
login_password:
description:
- The password of the user to log in to the instance
required: true
notes:
- Requires the mssql-tools package on the remote host.
requirements:
- python >= 2.7
- mssql-tools
'''.replace('\t', ' ')
EXAMPLES = '''
# Creates an AG listener named foo for the AG named bar with IP 1.2.3.4, and have all replicas in the "servers" group participate in read-only routing
- mssql_ag_listener:
name: foo
ag_name: bar
ip:
- '1.2.3.4'
readonly_routing_replicas: "{{ groups['servers'] }}"
login_name: sa
login_password: password
'''.replace('\t', ' ')
RETURN = '''
name:
description: The name of the AG listener that was created
returned: success
type: string
sample: foo
'''.replace('\t', ' ')
from ansible.module_utils.basic import AnsibleModule
import subprocess
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required = True),
ag_name = dict(required = True),
ip = dict(type = 'list', required = True),
readonly_routing_replicas = dict(type = 'list', required = True),
login_port = dict(required = False, default = 1433),
login_name = dict(required = True),
login_password = dict(required = True, no_log = True)
)
)
name = module.params['name']
ag_name = module.params['ag_name']
ips = module.params['ip']
readonly_routing_replicas = module.params['readonly_routing_replicas']
login_port = module.params['login_port']
login_name = module.params['login_name']
login_password = module.params['login_password']
sqlcmd(login_port, login_name, login_password, """
IF EXISTS (
SELECT *
FROM
sys.availability_groups ag JOIN
sys.availability_group_listeners agl ON ag.group_id = agl.group_id
WHERE
ag.name = {0} AND agl.dns_name = {2}
)
ALTER AVAILABILITY GROUP {1} REMOVE LISTENER {2}
;
ALTER AVAILABILITY GROUP {1} ADD LISTENER {2} (WITH IP ({3}))
""".format(
quoteName(ag_name, "'"),
quoteName(ag_name, '['),
quoteName(name, "'"),
', '.join("({0}, '255.255.255.255')".format(quoteName(ip, "'")) for ip in ips)
))
for replica in readonly_routing_replicas:
sqlcmd(login_port, login_name, login_password, """
ALTER AVAILABILITY GROUP {0} MODIFY REPLICA ON {1} WITH (PRIMARY_ROLE (ALLOW_CONNECTIONS = READ_WRITE))
;
ALTER AVAILABILITY GROUP {0} MODIFY REPLICA ON {1} WITH (SECONDARY_ROLE (ALLOW_CONNECTIONS = READ_ONLY))
;
ALTER AVAILABILITY GROUP {0} MODIFY REPLICA ON {1} WITH (SECONDARY_ROLE (READ_ONLY_ROUTING_URL = {2}))
;
""".format(
quoteName(ag_name, '['),
quoteName(replica.split('.')[0], "'"),
quoteName('tcp://{0}:{1}'.format(replica, login_port), "'")
))
for replica in readonly_routing_replicas:
sqlcmd(login_port, login_name, login_password, """
ALTER AVAILABILITY GROUP {0} MODIFY REPLICA ON {1} WITH (PRIMARY_ROLE (READ_ONLY_ROUTING_LIST = ({2})))
""".format(
quoteName(ag_name, '['),
quoteName(replica.split('.')[0], "'"),
', '.join(quoteName(other_replica.split('.')[0], "'") for other_replica in readonly_routing_replicas if other_replica != replica)
))
module.exit_json(changed = True, name = name)
def sqlcmd(login_port, login_name, login_password, command):
subprocess.check_call([
'/opt/mssql-tools/bin/sqlcmd',
'-S',
"localhost,{0}".format(login_port),
'-U',
login_name,
'-P',
login_password,
'-b',
'-Q',
command
])
def quoteName(name, quote_char):
if quote_char == '[' or quote_char == ']':
(quote_start_char, quote_end_char) = ('[', ']')
elif quote_char == "'":
(quote_start_char, quote_end_char) = ("N'", "'")
else:
raise Exception("Unsupported quote_char {0}, must be [ or ] or '".format(quote_char))
return "{0}{1}{2}".format(quote_start_char, name.replace(quote_end_char, quote_end_char + quote_end_char), quote_end_char)
if __name__ == '__main__':
main()
@@ -0,0 +1,169 @@
#!/usr/bin/python
# Copyright (c) 2017 Microsoft Corporation
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'supported_by': 'community',
'status': ['preview']
}
DOCUMENTATION = '''
---
module: mssql_certificate
short_description: Add certificates to a SQL Server instance
description:
- Add certificates to a SQL Server instance.
version_added: "2.2"
author: Arnav Singh (@arsing)
options:
name:
description:
- The name of the certificate to add
required: true
authorization_username:
description:
- The name of the SQL user to authorize with this certificate
required: false
default: 0.0.0.0
pub_key_path:
description:
- The path of the public key of the certificate (in Windows form)
required: true
priv_key_path:
description:
- The path of the private key of the certificate (in Windows form)
required: true
priv_key_password:
description:
- The password to decrypt the private key of the certificate
required: true
login_port:
description:
- The TDS port of the instance
required: false
default: 1433
login_name:
description:
- The name of the user to log in to the instance
required: true
login_password:
description:
- The password of the user to log in to the instance
required: true
notes:
- Requires the mssql-tools package on the remote host.
requirements:
- python >= 2.7
- mssql-tools
'''.replace('\t', ' ')
EXAMPLES = '''
- mssql_certificate:
name: dbm_cert
authorization_username: dbm_user
pub_key_path: "C:\\var\\opt\\mssql\\secrets\\dbm_certificate.cer"
priv_key_path: "C:\\var\\opt\\mssql\\secrets\\dbm_certificate.pvk"
priv_key_password: password
login_name: sa
login_password: password
'''.replace('\t', ' ')
RETURN = '''
name:
description: The name of the certificate that was added
returned: success
type: string
sample: foo
'''.replace('\t', ' ')
from ansible.module_utils.basic import AnsibleModule
import subprocess
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required = True),
authorization_username = dict(required = True),
pub_key_path = dict(required = True),
priv_key_path = dict(required = True),
priv_key_password = dict(required = True, no_log = True),
login_port = dict(required = False, default = 1433),
login_name = dict(required = True),
login_password = dict(required = True, no_log = True)
)
)
name = module.params['name']
authorization_username = module.params['authorization_username']
pub_key_path = module.params['pub_key_path']
priv_key_path = module.params['priv_key_path']
priv_key_password = module.params['priv_key_password']
login_port = module.params['login_port']
login_name = module.params['login_name']
login_password = module.params['login_password']
sqlcmd(login_port, login_name, login_password, """
IF NOT EXISTS(
SELECT * FROM sys.certificates WHERE name = {0}
)
CREATE CERTIFICATE {1}
AUTHORIZATION {2}
FROM FILE = {3}
WITH PRIVATE KEY (
FILE = {4},
DECRYPTION BY PASSWORD = {5}
)
;
""".format(
quoteName(name, "'"),
quoteName(name, '['),
quoteName(authorization_username, '['),
quoteName(pub_key_path, "'"),
quoteName(priv_key_path, "'"),
quoteName(priv_key_password, "'")
))
module.exit_json(changed = True, name = name)
def sqlcmd(login_port, login_name, login_password, command):
subprocess.check_call([
'/opt/mssql-tools/bin/sqlcmd',
'-S',
"localhost,{0}".format(login_port),
'-U',
login_name,
'-P',
login_password,
'-b',
'-Q',
command
])
def quoteName(name, quote_char):
if quote_char == '[' or quote_char == ']':
(quote_start_char, quote_end_char) = ('[', ']')
elif quote_char == "'":
(quote_start_char, quote_end_char) = ("N'", "'")
else:
raise Exception("Unsupported quote_char {0}, must be [ or ] or '".format(quote_char))
return "{0}{1}{2}".format(quote_start_char, name.replace(quote_end_char, quote_end_char + quote_end_char), quote_end_char)
if __name__ == '__main__':
main()
@@ -0,0 +1,138 @@
#!/usr/bin/python
# Copyright (c) 2017 Microsoft Corporation
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'supported_by': 'community',
'status': ['preview']
}
DOCUMENTATION = '''
---
module: mssql_conf
short_description: Set configuration settings for a SQL Server instance
description:
- Set configuration settings for a SQL Server instance
version_added: "2.2"
author: Arnav Singh (@arsing)
options:
setup_sa_password:
description:
- The password to set for the sa account in setup
required: false
name:
description:
- The name of the setting
required: false
value:
description:
- The value of the setting
required: false
login_port:
description:
- The TDS port of the instance
required: false
default: 1433
login_name:
description:
- The name of the user to log in to the instance
required: true
login_password:
description:
- The password of the user to log in to the instance
required: true
requirements:
- python >= 2.7
'''.replace('\t', ' ')
EXAMPLES = '''
# Enables HADRON
- mssql_conf:
name: hadr.hadrenabled
value: 1
login_name: sa
login_password: password
'''.replace('\t', ' ')
RETURN = '''
#
'''.replace('\t', ' ')
from ansible.module_utils.basic import AnsibleModule
import os.path
import subprocess
def main():
module = AnsibleModule(
argument_spec = dict(
setup_sa_password = dict(required = False, no_log = True),
setup_pid = dict(required = False),
name = dict(required = False),
value = dict(required = False),
traceflags_on = dict(type = 'list', required = False),
traceflags_off = dict(type = 'list', required = False),
login_port = dict(required = False, default = 1433),
login_name = dict(required = True),
login_password = dict(required = True, no_log = True)
),
required_one_of = [
['setup_sa_password', 'name', 'traceflags_on', 'traceflags_off']
],
mutually_exclusive = [
['setup_sa_password', 'name', 'traceflags_on'],
['setup_sa_password', 'name', 'traceflags_off'],
],
required_together = [
['setup_sa_password', 'setup_pid'],
['name', 'value']
],
)
setup_sa_password = module.params['setup_sa_password']
setup_pid = module.params['setup_pid']
name = module.params['name']
value = module.params['value']
traceflags_on = module.params['traceflags_on']
traceflags_off = module.params['traceflags_off']
login_port = module.params['login_port']
login_name = module.params['login_name']
login_password = module.params['login_password']
changed = True
if setup_sa_password is not None:
if os.path.isfile('/var/opt/mssql/mssql.conf'):
changed = False
else:
setup_env = os.environ.copy()
subprocess.check_call(
['/opt/mssql/bin/mssql-conf', '--noprompt', 'setup', 'accept-eula'],
env = { 'MSSQL_SA_PASSWORD': setup_sa_password, 'MSSQL_PID': setup_pid })
if name is not None:
subprocess.check_call(['/opt/mssql/bin/mssql-conf', '--noprompt', 'set', name, value])
if traceflags_on is not None:
subprocess.check_call(['/opt/mssql/bin/mssql-conf', '--noprompt', 'traceflag'] + [str(traceflag) for traceflag in traceflags_on] + ['on'])
if traceflags_off is not None:
subprocess.check_call(['/opt/mssql/bin/mssql-conf', '--noprompt', 'traceflag'] + [str(traceflag) for traceflag in traceflags_off] + ['off'])
module.exit_json(changed = changed)
if __name__ == '__main__':
main()
@@ -0,0 +1,201 @@
#!/usr/bin/python
# Copyright (c) 2017 Microsoft Corporation
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'supported_by': 'community',
'status': ['preview']
}
DOCUMENTATION = '''
---
module: mssql_login
short_description: Add endpoints to a SQL Server instance
description:
- Add endpoints to a SQL Server instance.
version_added: "2.2"
author: Arnav Singh (@arsing)
options:
name:
description:
- The name of the endpoint to add
required: true
ip:
description:
- The IP to bind to
required: false
default: 0.0.0.0
port:
description:
- The port to bind to
required: true
type:
description:
- The type of the endpoint
required: true
choices: ["DATA_MIRRORING"]
dbm_cert_name:
description:
- The name of the cert to use for the DATA_MIRRORING endpoint
required: false
default: []
state:
description:
- The state to set the endpoint to
required: false
choices:
- started
default: started
login_port:
description:
- The TDS port of the instance
required: false
default: 1433
login_name:
description:
- The name of the user to log in to the instance
required: true
login_password:
description:
- The password of the user to log in to the instance
required: true
notes:
- Requires the mssql-tools package on the remote host.
requirements:
- python >= 2.7
- mssql-tools
'''.replace('\t', ' ')
EXAMPLES = '''
# Create a DBM endpoint named 'foo' on port 5022 with authentication from the cert named 'bar'
- mssql_endpoint:
name: foo
port: 5022
type: DATA_MIRRORING
dbm_cert_name: bar
state: started
login_name: sa
login_password: password
'''.replace('\t', ' ')
RETURN = '''
name:
description: The name of the endpoint that was added
returned: success
type: string
sample: foo
'''.replace('\t', ' ')
from ansible.module_utils.basic import AnsibleModule
import subprocess
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required = True),
ip = dict(required = False, default = "0.0.0.0"),
port = dict(required = True),
type = dict(choices = ['DATA_MIRRORING'], required = True),
dbm_cert_name = dict(required = False),
state = dict(choices = ['started'], required = False, default = 'started'),
login_port = dict(required = False, default = 1433),
login_name = dict(required = True),
login_password = dict(required = True, no_log = True)
),
required_if = [
['type', 'DATA_MIRRORING', ['dbm_cert_name']]
]
)
name = module.params['name']
ip = module.params['ip']
port = module.params['port']
type = module.params['type']
dbm_cert_name = module.params['dbm_cert_name']
state = module.params['state']
login_port = module.params['login_port']
login_name = module.params['login_name']
login_password = module.params['login_password']
if type == "DATA_MIRRORING":
options = """
ROLE = ALL,
AUTHENTICATION = CERTIFICATE {0},
ENCRYPTION = REQUIRED ALGORITHM AES
""".format(
quoteName(dbm_cert_name, '[')
)
sqlcmd(login_port, login_name, login_password, """
IF NOT EXISTS(
SELECT * FROM sys.tcp_endpoints WHERE name = {0}
)
CREATE ENDPOINT {1}
AS TCP (LISTENER_IP = ({2}), LISTENER_PORT = {3})
FOR DATA_MIRRORING ({4})
;
""".format(
quoteName(name, "'"),
quoteName(name, '['),
ip,
port,
options
))
if state == 'started':
sqlcmd(login_port, login_name, login_password, """
IF NOT EXISTS(
SELECT * FROM sys.tcp_endpoints WHERE name = {0} AND state = 0
)
ALTER ENDPOINT {1} STATE = STARTED
;
""".format(
quoteName(name, "'"),
quoteName(name, '[')
))
module.exit_json(changed = True, name = name)
def sqlcmd(login_port, login_name, login_password, command):
subprocess.check_call([
'/opt/mssql-tools/bin/sqlcmd',
'-S',
"localhost,{0}".format(login_port),
'-U',
login_name,
'-P',
login_password,
'-b',
'-Q',
command
])
def quoteName(name, quote_char):
if quote_char == '[' or quote_char == ']':
(quote_start_char, quote_end_char) = ('[', ']')
elif quote_char == "'":
(quote_start_char, quote_end_char) = ("N'", "'")
else:
raise Exception("Unsupported quote_char {0}, must be [ or ] or '".format(quote_char))
return "{0}{1}{2}".format(quote_start_char, name.replace(quote_end_char, quote_end_char + quote_end_char), quote_end_char)
if __name__ == '__main__':
main()
@@ -0,0 +1,140 @@
#!/usr/bin/python
# Copyright (c) 2017 Microsoft Corporation
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'supported_by': 'community',
'status': ['preview']
}
DOCUMENTATION = '''
---
module: mssql_grant_endpoint
short_description: Grants permissions on endpoints of a SQL Server instance
description:
- Grants permissions on endpoints of a SQL Server instance.
version_added: "2.2"
author: Arnav Singh (@arsing)
options:
name:
description:
- The name of the endpoint
required: true
permission:
description:
- The permission to grant on the endpoint
required: true
principal:
description:
- The principal to grant the permission to
required: true
login_port:
description:
- The TDS port of the instance
required: false
default: 1433
login_name:
description:
- The name of the user to log in to the instance
required: true
login_password:
description:
- The password of the user to log in to the instance
required: true
notes:
- Requires the mssql-tools package on the remote host.
requirements:
- python >= 2.7
- mssql-tools
'''.replace('\t', ' ')
EXAMPLES = '''
# Grants CONNECT permission on the DBM endpoint named 'foo' to the login 'bar'
- mssql_endpoint:
name: foo
permission: CONNECT
principal: bar
login_name: sa
login_password: password
'''.replace('\t', ' ')
RETURN = '''
name:
description: The name of the login that was added
returned: success
type: string
sample: foo
'''.replace('\t', ' ')
from ansible.module_utils.basic import AnsibleModule
import subprocess
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required = True),
permission = dict(choices = ["CONNECT"], required = True),
principal = dict(required = True),
login_port = dict(required = False, default = 1433),
login_name = dict(required = True),
login_password = dict(required = True, no_log = True)
)
)
permission = module.params['permission']
name = module.params['name']
principal = module.params['principal']
login_port = module.params['login_port']
login_name = module.params['login_name']
login_password = module.params['login_password']
sqlcmd(login_port, login_name, login_password, """
GRANT {0} ON ENDPOINT::{1} TO {2}
""".format(
permission,
quoteName(name, '['),
quoteName(principal, '[')
))
module.exit_json(changed = True, name = name)
def sqlcmd(login_port, login_name, login_password, command):
subprocess.check_call([
'/opt/mssql-tools/bin/sqlcmd',
'-S',
"localhost,{0}".format(login_port),
'-U',
login_name,
'-P',
login_password,
'-b',
'-Q',
command
])
def quoteName(name, quote_char):
if quote_char == '[' or quote_char == ']':
(quote_start_char, quote_end_char) = ('[', ']')
elif quote_char == "'":
(quote_start_char, quote_end_char) = ("N'", "'")
else:
raise Exception("Unsupported quote_char {0}, must be [ or ] or '".format(quote_char))
return "{0}{1}{2}".format(quote_start_char, name.replace(quote_end_char, quote_end_char + quote_end_char), quote_end_char)
if __name__ == '__main__':
main()
@@ -0,0 +1,160 @@
#!/usr/bin/python
# Copyright (c) 2017 Microsoft Corporation
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'supported_by': 'community',
'status': ['preview']
}
DOCUMENTATION = '''
---
module: mssql_login
short_description: Add logins to a SQL Server instance
description:
- Add logins to a SQL Server instance.
version_added: "2.2"
author: Arnav Singh (@arsing)
options:
name:
description:
- The name of the login to add
required: true
password:
description:
- The password of the login
required: true
roles:
description:
- The roles to add the login to
required: false
default: []
login_port:
description:
- The TDS port of the instance
required: false
default: 1433
login_name:
description:
- The name of the user to log in to the instance
required: true
login_password:
description:
- The password of the user to log in to the instance
required: true
notes:
- Requires the mssql-tools package on the remote host.
requirements:
- python >= 2.7
- mssql-tools
'''.replace('\t', ' ')
EXAMPLES = '''
# Create a login named 'foo' with password 'bar' and add it to sysadmin role
- mssql_login:
name: foo
password: bar
roles:
- sysadmin
login_name: sa
login_password: password
'''.replace('\t', ' ')
RETURN = '''
name:
description: The name of the login that was added
returned: success
type: string
sample: foo
'''.replace('\t', ' ')
from ansible.module_utils.basic import AnsibleModule
import subprocess
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required = True),
password = dict(required = True, no_log = True),
roles = dict(type = 'list', required = False, default = []),
login_port = dict(required = False, default = 1433),
login_name = dict(required = True),
login_password = dict(required = True, no_log = True)
)
)
name = module.params['name']
password = module.params['password']
roles = module.params['roles']
login_port = module.params['login_port']
login_name = module.params['login_name']
login_password = module.params['login_password']
sqlcmd(login_port, login_name, login_password, """
IF EXISTS(
SELECT * FROM sys.sql_logins WHERE name = {0}
)
ALTER LOGIN {1} WITH PASSWORD = {2}
ELSE
CREATE LOGIN {1} WITH
PASSWORD = {2},
DEFAULT_DATABASE = [master],
CHECK_EXPIRATION = OFF,
CHECK_POLICY = OFF
;
""".format(
quoteName(name, "'"),
quoteName(name, '['),
quoteName(password, "'")
))
for role in roles:
sqlcmd(login_port, login_name, login_password, """
ALTER SERVER ROLE {0} ADD MEMBER {1}
""".format(
quoteName(role, '['),
quoteName(name, '[')
))
module.exit_json(changed = True, name = name)
def sqlcmd(login_port, login_name, login_password, command):
subprocess.check_call([
'/opt/mssql-tools/bin/sqlcmd',
'-S',
"localhost,{0}".format(login_port),
'-U',
login_name,
'-P',
login_password,
'-b',
'-Q',
command
])
def quoteName(name, quote_char):
if quote_char == '[' or quote_char == ']':
(quote_start_char, quote_end_char) = ('[', ']')
elif quote_char == "'":
(quote_start_char, quote_end_char) = ("N'", "'")
else:
raise Exception("Unsupported quote_char {0}, must be [ or ] or '".format(quote_char))
return "{0}{1}{2}".format(quote_start_char, name.replace(quote_end_char, quote_end_char + quote_end_char), quote_end_char)
if __name__ == '__main__':
main()
@@ -0,0 +1,123 @@
#!/usr/bin/python
# Copyright (c) 2017 Microsoft Corporation
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'supported_by': 'community',
'status': ['preview']
}
DOCUMENTATION = '''
---
module: mssql_master_key
short_description: Add master keys to a SQL Server instance
description:
- Add master keys to a SQL Server instance.
version_added: "2.2"
author: Arnav Singh (@arsing)
options:
password:
description:
- The password of the master key
required: true
login_port:
description:
- The TDS port of the instance
required: false
default: 1433
login_name:
description:
- The name of the user to log in to the instance
required: true
login_password:
description:
- The password of the user to log in to the instance
required: true
notes:
- Requires the mssql-tools package on the remote host.
requirements:
- python >= 2.7
- mssql-tools
'''.replace('\t', ' ')
EXAMPLES = '''
# Create a master key with password 'foo'
- mssql_master_key:
password: foo
login_name: sa
login_password: password
'''.replace('\t', ' ')
RETURN = '''
#
'''.replace('\t', ' ')
from ansible.module_utils.basic import AnsibleModule
import subprocess
def main():
module = AnsibleModule(
argument_spec = dict(
password = dict(required = True, no_log = True),
login_port = dict(required = False, default = 1433),
login_name = dict(required = True),
login_password = dict(required = True, no_log = True)
)
)
password = module.params['password']
login_port = module.params['login_port']
login_name = module.params['login_name']
login_password = module.params['login_password']
sqlcmd(login_port, login_name, login_password, """
IF EXISTS (
SELECT * FROM sys.symmetric_keys WHERE name = '##MS_DatabaseMasterKey##'
)
ALTER MASTER KEY REGENERATE WITH ENCRYPTION BY PASSWORD = {0}
ELSE
CREATE MASTER KEY ENCRYPTION BY PASSWORD = {0}
""".format(
quoteName(password, "'")
))
module.exit_json(changed = True)
def sqlcmd(login_port, login_name, login_password, command):
subprocess.check_call([
'/opt/mssql-tools/bin/sqlcmd',
'-S',
"localhost,{0}".format(login_port),
'-U',
login_name,
'-P',
login_password,
'-b',
'-Q',
command
])
def quoteName(name, quote_char):
if quote_char == '[' or quote_char == ']':
(quote_start_char, quote_end_char) = ('[', ']')
elif quote_char == "'":
(quote_start_char, quote_end_char) = ("N'", "'")
else:
raise Exception("Unsupported quote_char {0}, must be [ or ] or '".format(quote_char))
return "{0}{1}{2}".format(quote_start_char, name.replace(quote_end_char, quote_end_char + quote_end_char), quote_end_char)
if __name__ == '__main__':
main()
@@ -0,0 +1,136 @@
#!/usr/bin/python
# Copyright (c) 2017 Microsoft Corporation
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'supported_by': 'community',
'status': ['preview']
}
DOCUMENTATION = '''
---
module: mssql_user
short_description: Add users to a SQL Server instance
description:
- Add ysers to a SQL Server instance.
version_added: "2.2"
author: Arnav Singh (@arsing)
options:
name:
description:
- The name of the user to add
required: true
login:
description:
- The login name of the user to add
required: true
login_port:
description:
- The TDS port of the instance
required: false
default: 1433
login_name:
description:
- The name of the user to log in to the instance
required: true
login_password:
description:
- The password of the user to log in to the instance
required: true
notes:
- Requires the mssql-tools package on the remote host.
requirements:
- python >= 2.7
- mssql-tools
'''.replace('\t', ' ')
EXAMPLES = '''
# Create a user named 'foo' for the login named 'bar'
- mssql_user:
name: foo
login: bar
login_name: sa
login_password: password
'''.replace('\t', ' ')
RETURN = '''
name:
description: The name of the user that was added
returned: success
type: string
sample: foo
'''.replace('\t', ' ')
from ansible.module_utils.basic import AnsibleModule
import subprocess
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required = True),
login = dict(required = True),
login_port = dict(required = False, default = 1433),
login_name = dict(required = True),
login_password = dict(required = True, no_log = True)
)
)
name = module.params['name']
login = module.params['login']
login_port = module.params['login_port']
login_name = module.params['login_name']
login_password = module.params['login_password']
sqlcmd(login_port, login_name, login_password, """
IF NOT EXISTS(
SELECT * FROM sys.sysusers WHERE name = {0}
)
CREATE USER {1} FOR LOGIN {2}
;
""".format(
quoteName(name, "'"),
quoteName(name, '['),
quoteName(login, '[')
))
module.exit_json(changed = True, name = name)
def sqlcmd(login_port, login_name, login_password, command):
subprocess.check_call([
'/opt/mssql-tools/bin/sqlcmd',
'-S',
"localhost,{0}".format(login_port),
'-U',
login_name,
'-P',
login_password,
'-b',
'-Q',
command
])
def quoteName(name, quote_char):
if quote_char == '[' or quote_char == ']':
(quote_start_char, quote_end_char) = ('[', ']')
elif quote_char == "'":
(quote_start_char, quote_end_char) = ("N'", "'")
else:
raise Exception("Unsupported quote_char {0}, must be [ or ] or '".format(quote_char))
return "{0}{1}{2}".format(quote_start_char, name.replace(quote_end_char, quote_end_char + quote_end_char), quote_end_char)
if __name__ == '__main__':
main()
@@ -0,0 +1,75 @@
---
# Usage:
# ansible-playbook ./play.yml -i ./inventory --ask-vault-pass -e 'ansible_user=<>'
- hosts: servers
vars:
# The URL of the repo to fetch the mssql-server and mssql-server-ha packages from
centos_server_repo_url: 'https://packages.microsoft.com/config/rhel/7/mssql-server.repo'
ubuntu_server_repo_url: 'https://packages.microsoft.com/config/ubuntu/16.04/mssql-server.list'
# The URL of the repo to fetch the mssql-tools package from
centos_tools_repo_url: 'https://packages.microsoft.com/config/rhel/7/prod.repo'
ubuntu_tools_repo_url: 'https://packages.microsoft.com/config/ubuntu/16.04/prod.list'
# The sqlservr PID. Only used if mssql-server needs to be installed.
pid: 'Developer'
# The port for the TSQL endpoint
tsql_endpoint_port: 1433
# The name of the DBM endpoint
dbm_endpoint_name: 'dbm_endpoint'
# The port for the DBM endpoint
dbm_endpoint_port: 5022
# The SQL login and SQL username for the DBM endpoint user
dbm_login: 'dbm_login'
dbm_username: 'dbm_user'
# The name of the DBM cert
dbm_cert_name: 'dbm_cert'
# The path of the DBM cert public key accessible *from the machine running this playbook*.
# The path need not be accessible from the nodes that are being deployed to.
dbm_cert_pub: /share/dbm_certificate.cer
# The path of the DBM cert private key accessible *from the machine running this playbook*.
# The path need not be accessible from the nodes that are being deployed to.
dbm_cert_priv: /share/dbm_certificate.pvk
# The filename to store the DBM cert public key as (/var/opt/mssql/data/<this value>)
dbm_cert_pub_target: dbm_certificate.cer
# The filename to store the DBM cert private key as (/var/opt/mssql/data/<this value>)
dbm_cert_priv_target: dbm_certificate.pvk
# The name of the AG and AG pacemaker resource
ag_name: 'ag1'
# The IP of the AG listener
ag_listener_ip: ''
# The NIC that the AG listener IP should be set on
ag_listener_nic: 'eno1'
# The name of the pacemaker cluster
pacemaker_cluster_name: 'mycluster'
# The SQL login for the pacemaker user
pacemaker_login: 'pacemaker'
vars_files:
- 'vault.yml'
become: yes
become_user: root
become_method: sudo
any_errors_fatal: true
max_fail_percentage: 0
roles:
- mssql-server-ag-external
@@ -0,0 +1,5 @@
---
dependencies:
- role: pacemaker
- role: mssql-server-ha
@@ -0,0 +1,94 @@
---
# install pacemaker resource agents
- name: install mssql-server-ha package
package:
name: mssql-server-ha
state: latest
# create AG
- name: create AG with all replicas in secondary role
mssql_ag:
name: "{{ ag_name }}"
state: all_secondaries_or_unjoined
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
- name: promote one replica to AG primary and join others to it
mssql_ag:
name: "{{ ag_name }}"
state: all_joined_to_one_primary
all_replicas: "{{ groups['servers'] }}"
primary: "{{ groups['servers'][0] }}"
local_replica: "{{ inventory_hostname }}"
dbm_endpoint_port: "{{ dbm_endpoint_port }}"
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
# create listener
- name: create listener
mssql_ag_listener:
name: "{{ ag_name }}_listener"
ag_name: "{{ ag_name }}"
ip:
- "{{ ag_listener_ip }}"
readonly_routing_replicas: "{{ groups['servers'] }}"
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
when: inventory_hostname == groups['servers'][0] and ag_listener_ip != ''
# create pacemaker login
- name: create pacemaker login
mssql_login:
name: "{{ pacemaker_login }}"
password: "{{ pacemaker_password }}"
roles:
- sysadmin
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
- name: create pacemaker passwd file
copy:
content: |
{{ pacemaker_login }}
{{ pacemaker_password }}
dest: /var/opt/mssql/secrets/passwd
mode: 0400
# create AG pacemaker resource
- name: create AG pacemaker resource
shell: |
pcs resource show '{{ ag_name }}' ||
pcs resource create '{{ ag_name }}' ocf:mssql:ag \
'ag_name={{ ag_name }}' \
--master meta \
master-max=1 master-node-max=1 clone-max={{ groups['servers']|length }} notify=true
when: inventory_hostname == groups['servers'][0]
# Create AG listener pacemaker resource
- name: create AG listener pacemaker resource
shell: |
pcs resource show '{{ ag_name }}_listener' || \
pcs resource create '{{ ag_name }}_listener' ocf:heartbeat:IPaddr2 \
'ip={{ ag_listener_ip }}' \
'nic={{ ag_listener_nic }}'
when: inventory_hostname == groups['servers'][0]
- name: colocate AG listener with AG primary
command: pcs constraint colocation add '{{ ag_name }}_listener' with master '{{ ag_name }}'-master
when: inventory_hostname == groups['servers'][0]
@@ -0,0 +1,4 @@
---
dependencies:
- role: mssql-server
@@ -0,0 +1,118 @@
---
# enable HA
- name: check HA enabled
command: /opt/mssql-tools/bin/sqlcmd -U sa -P '{{ sa_password }}' -b -Q "SELECT 'IsHadrEnabled = ' + CONVERT(NVARCHAR(100), SERVERPROPERTY('IsHadrEnabled'))"
register: hadr_enabled
- name: enable HA
mssql_conf:
name: hadr.hadrenabled
value: 1
login_name: 'sa'
login_password: "{{ sa_password }}"
when: hadr_enabled.stdout.find('IsHadrEnabled = 1') == -1
- name: restart sqlservr to enable HA
service:
name: mssql-server
state: restarted
when: hadr_enabled.stdout.find('IsHadrEnabled = 1') == -1
# DBM endpoint
- name: check if firewalld is installed (CentOS, RedHat)
command: rpm -q firewalld
register: firewalld_installed
failed_when: false
when: ansible_distribution in ['CentOS', 'RedHat']
- name: open DBM endpoint in firewall (CentOS, RedHat)
firewalld:
port: "{{ dbm_endpoint_port }}/tcp"
state: enabled
permanent: true
when: (ansible_distribution in ['CentOS', 'RedHat']) and firewalld_installed.rc == 0
- name: reload firewall (CentOS, RedHat)
command: firewall-cmd --reload
when: (ansible_distribution in ['CentOS', 'RedHat']) and firewalld_installed.rc == 0
- name: open DBM endpoint in firewall (Ubuntu)
ufw:
port: "{{ dbm_endpoint_port }}"
proto: tcp
rule: allow
when: ansible_distribution == 'Ubuntu'
- name: copy DBM cert public key
copy:
src: "{{ dbm_cert_pub }}"
dest: "/var/opt/mssql/data/{{ dbm_cert_pub_target }}"
owner: mssql
group: mssql
mode: 0444
- name: copy DBM cert private key
copy:
src: "{{ dbm_cert_priv }}"
dest: "/var/opt/mssql/data/{{ dbm_cert_priv_target }}"
owner: mssql
group: mssql
mode: 0400
- name: create master key
mssql_master_key:
password: "{{ master_key_password }}"
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
- name: create DBM endpoint login
mssql_login:
name: "{{ dbm_login }}"
password: "{{ dbm_password }}"
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
- name: create DBM endpoint user
mssql_user:
name: "{{ dbm_username }}"
login: "{{ dbm_login }}"
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
- name: create DBM endpoint certificate
mssql_certificate:
name: "{{ dbm_cert_name }}"
authorization_username: "{{ dbm_username }}"
pub_key_path: "/var/opt/mssql/data/{{ dbm_cert_pub_target }}"
priv_key_path: "/var/opt/mssql/data/{{ dbm_cert_priv_target }}"
priv_key_password: "{{ dbm_cert_password }}"
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
- name: create DBM endpoint
mssql_endpoint:
name: "{{ dbm_endpoint_name }}"
port: "{{ dbm_endpoint_port }}"
type: DATA_MIRRORING
dbm_cert_name: "{{ dbm_cert_name }}"
state: started
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
- name: grant connect on DBM endpoint
mssql_grant_endpoint:
name: "{{ dbm_endpoint_name }}"
permission: CONNECT
principal: "{{ dbm_login }}"
login_port: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
@@ -0,0 +1,102 @@
---
# install mssql-server package
- name: install mssql-server repo (CentOS, RedHat)
get_url:
url: "{{ centos_server_repo_url }}"
dest: /etc/yum.repos.d/mssql-server.repo
when: ansible_distribution in ['CentOS', 'RedHat']
- name: install mssql-server repo (Ubuntu)
get_url:
url: "{{ ubuntu_server_repo_url }}"
dest: /etc/apt/sources.list.d/mssql-server.list
when: ansible_distribution == 'Ubuntu'
- name: refresh apt-get cache for server repo (Ubuntu)
command: apt-get update
when: ansible_distribution == 'Ubuntu'
- name: install mssql-server package
package:
name: mssql-server
state: latest
# setup
- name: mssql-server setup
mssql_conf:
setup_sa_password: "{{ sa_password }}"
setup_pid: "{{ pid }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
# TSQL endpoint
- name: set TSQL endpoint port
mssql_conf:
name: network.tcpport
value: "{{ tsql_endpoint_port }}"
login_name: 'sa'
login_password: "{{ sa_password }}"
- name: check if firewalld is installed (CentOS, RedHat)
command: rpm -q firewalld
register: firewalld_installed
failed_when: false
when: ansible_distribution in ['CentOS', 'RedHat']
- name: open TSQL endpoint in firewall (CentOS, RedHat)
firewalld:
port: "{{ tsql_endpoint_port }}/tcp"
state: enabled
permanent: true
when: (ansible_distribution in ['CentOS', 'RedHat']) and firewalld_installed.rc == 0
- name: reload firewall (CentOS, RedHat)
command: firewall-cmd --reload
when: (ansible_distribution in ['CentOS', 'RedHat']) and firewalld_installed.rc == 0
- name: open TSQL endpoint in firewall (Ubuntu)
ufw:
port: "{{ tsql_endpoint_port }}"
proto: tcp
rule: allow
when: ansible_distribution == 'Ubuntu'
# mssql-tools package
- name: install mssql-tools repo (CentOS, RedHat)
get_url:
url: "{{ centos_tools_repo_url }}"
dest: /etc/yum.repos.d/mssql-tools.repo
when: ansible_distribution in ['CentOS', 'RedHat']
- name: install mssql-tools repo (Ubuntu)
get_url:
url: "{{ ubuntu_tools_repo_url }}"
dest: /etc/apt/sources.list.d/mssql-tools.list
when: ansible_distribution == 'Ubuntu'
- name: refresh apt-get cache for tools repo (Ubuntu)
command: apt-get update
when: ansible_distribution == 'Ubuntu'
- name: install mssql-tools package
package:
name: mssql-tools
state: latest
environment:
ACCEPT_EULA: 'y'
# Start mssql-server service
- name: start sqlservr
service:
name: mssql-server
state: started
@@ -0,0 +1,96 @@
---
# enable HA repo
- name: Enable HA repo (RedHat)
command: subscription-manager repos --enable=rhel-ha-for-rhel-7-server-rpms
when: ansible_distribution == 'RedHat'
# install pacemaker
- name: ensure pacemaker is installed (CentOS, RedHat)
package:
name:
- fence-agents-all
- pacemaker
- pcs
- resource-agents
state: latest
when: ansible_distribution in ['CentOS', 'RedHat']
- name: ensure pacemaker is installed (Ubuntu)
package:
name:
- fence-agents
- pacemaker
- pcs
- resource-agents
state: latest
when: ansible_distribution == 'Ubuntu'
- name: check if firewalld is installed (CentOS, RedHat)
command: rpm -q firewalld
register: firewalld_installed
failed_when: false
when: ansible_distribution in ['CentOS', 'RedHat']
- name: open pacemaker in firewall (CentOS, RedHat)
firewalld:
service: high-availability
state: enabled
permanent: true
immediate: true
when: (ansible_distribution in ['CentOS', 'RedHat']) and firewalld_installed.rc == 0
- name: reload firewall (CentOS, RedHat)
command: firewall-cmd --reload
when: (ansible_distribution in ['CentOS', 'RedHat']) and firewalld_installed.rc == 0
- name: open pacemaker in firewall (TCP) (Ubuntu)
ufw:
port: "{{ item }}"
proto: tcp
rule: allow
with_items:
- 2224
- 3121
- 21064
when: ansible_distribution == 'Ubuntu'
- name: open pacemaker in firewall (UDP) (Ubuntu)
ufw:
port: 5405
proto: udp
rule: allow
when: ansible_distribution == 'Ubuntu'
- name: setup pacemaker admin user
user:
name: hacluster
password: "{{ pacemaker_cluster_password|password_hash('sha512') }}"
- name: setup pcsd service
service:
name: pcsd
state: started
enabled: yes
- name: setup pacemaker service
service:
name: pacemaker
enabled: yes
# create pacemaker cluster
- name: create pacemaker cluster
shell: |
pcs cluster auth -u hacluster -p '{{ pacemaker_cluster_password }}' {% for server in groups['servers'] %} {{ server }} {% endfor %} &&
pcs cluster setup --name '{{ pacemaker_cluster_name }}' {% for server in groups['servers'] %} {{ server }} {% endfor %} &&
pcs cluster start --all &&
pcs property set stonith-enabled=false &&
pcs property set start-failure-is-fatal=false
args:
creates: /etc/corosync/corosync.conf
when: inventory_hostname == groups['servers'][0]