docs/main/deployment-guide/disaster-recovery-aws.mdx
Before you begin, ensure the following are in place:
Enterprise customers who use Mattermost for mission-critical operations must ensure continuous availability and operational resilience. A robust disaster recovery strategy is essential to mitigate risks associated with data center failures, ensuring that users can access Mattermost seamlessly, even in the event of unexpected outages.
This page details the steps needed to set up Mattermost in an active/passive disaster recovery configuration on AWS, and how to fail over from one data center to another.
<Tip>To learn how to safely upgrade your deployment in Kubernetes for High Availability and Active/Active support, see the Upgrading Mattermost in Kubernetes and High Availability Environments documentation.
</Tip>As a first step, set up Mattermost in a single data center. The following diagram illustrates a basic single data center architecture:
The diagram above has a single proxy, forwarding traffic to 2 nodes. There's also a database with single writer + n readers and an S3 bucket and ES/OS using AWS OpenSearch service.
At this stage, we are ignoring other details like LDAP/SAML, SMTP etc.
<Tip>The following architecture would be implemented when an entire region goes down. It does not cover the case when a single server/service goes down. For example:
The next tasks include creating a global AWS Cluster.
Select the Enable write forwarding option on the secondary cluster to help forward write operations from secondary to primary. See the AmazonRDS write forwarding documentation for details.
Also verify the PostgreSQL version and ensure it allows write forwarding. Not all PostgreSQL versions allow it. See the Amazon RDS write forwarding region and version availability documentation for details.
You should now have a global cluster with the primary cluster in us-west-1, and the secondary cluster in us-east-1:
Select the Replica modification sync option for the bucket to help keep the replica and source buckets in sync with each other.
</Warning>Now you have bi-directional replication working between these S3 replica and source buckets.
<Tip>
- Elasticsearch 7.10 or OpenSearch 2.x
- Fine-grained access control enabled
- Node-to-node encryption enabled
If you are already running OpenSearch 2.x, all you need to do is enable fine-grained access control — node-to-node encryption is enabled automatically once fine-grained access control is turned on.
</Tip>CrossClusterGet permission on the IAM policy for the OS cluster set under the Security Configuration tab for your OS domain. We recommend the following as per AWS, but feel free to fine-tune as necessary:json{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "es:ESHttp*", "Resource": "arn:aws:es:<region>:<acc_num>:domain/<domain_name>/*" }, { "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "es:ESCrossClusterGet", "Resource": "arn:aws:es:<region>:<acc_num>:domain/<domain_name>" } ] }
To recap:
After creating the master user, IP based access to the OS might not work from Mattermost application nodes. You may need to update the ElasticsearchSettings section in config.json to update the server username and password.
<div class="warning"> <div class="title">Warning
</div>At this stage, ensure that you have all indices populated with data in the primary region. Run a bulk index to do that if you haven't already.
</div>
- First, create a connection from secondary to primary. Note that replication in OS works in a "pull" model, so the secondary site pulls data from the primary.
- In the Amazon OpenSearch Service console, select the secondary domain, go to the Connections tab, and choose Request.
- For Connection alias, enter a name for your connection.
- Choose connect to a domain in another AWS account or region, and enter the ARN of the primary domain.
- Select Request to send a permission request to the primary domain.
- Open the primary domain to see and accept the incoming request under the Connections tab.
SSH into an app node in the secondary region to set up an auto-follow rule for the
posts*indices because of the daily naming scheme and monthly aggregation.For the other indices, replicate each of them. You can also set up a rule with
*to replicate everything, but that would also include the hidden and system indices which you don't want.Set up the auto-follow for
posts*indices:shcurl -XPOST -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/_autofollow?pretty' -d ' { "leader_alias" : "<LEADER_ALIAS>", "name": "autofollow-rule", "pattern": "posts*", "use_roles":{ "leader_cluster_role": "all_access", "follower_cluster_role": "all_access" } }'
Check the status of the auto-follow rule:
shcurl -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/autofollow_stats?pretty' { "num_success_start_replication" : 2, "num_failed_start_replication" : 0, "num_failed_leader_calls" : 0, "failed_indices" : [ ], "autofollow_stats" : [ { "name" : "autofollow-rule", "pattern" : "posts*", "num_success_start_replication" : 2, "num_failed_start_replication" : 0, "num_failed_leader_calls" : 0, "failed_indices" : [ ], "last_execution_time" : 1737699113927 } ] }
Next, set up replication for the other indices:
shcurl -XPUT -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/channels/_start?pretty' -d ' { "leader_alias": "<LEADER_ALIAS>", "leader_index": "channels", "use_roles":{ "leader_cluster_role": "all_access", "follower_cluster_role": "all_access" } }' curl -XPUT -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/users/_start?pretty' -d ' { "leader_alias": "<LEADER_ALIAS>", "leader_index": "users", "use_roles":{ "leader_cluster_role": "all_access", "follower_cluster_role": "all_access" } }' curl -XPUT -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/files/_start?pretty' -d ' { "leader_alias": "<LEADER_ALIAS>", "leader_index": "files", "use_roles":{ "leader_cluster_role": "all_access", "follower_cluster_role": "all_access" } }'
Check the status of the replication rules:
shcurl -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/channels/_status?pretty' curl -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/files/_status?pretty' curl -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/users/_status?pretty' curl -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/posts_<DATE>/_status?pretty' Sample output: { "status" : "SYNCING", "reason" : "User initiated", "leader_alias" : "<LEADER_ALIAS>", "leader_index" : "<INDEX>", "follower_index" : "<INDEX>", "syncing_details" : { "leader_checkpoint" : 16, "follower_checkpoint" : 16, "seq_no" : 17 } }
Check for indices. You should be able to see all the indices from the primary domain in the secondary domain:
shcurl -s -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_cat/indices?pretty'
If the job scheduler is left running in the secondary region, it will pick up jobs and start running them. Follow these steps to manage it correctly. See the RunScheduler configuration setting documentation for details.
JobSettings.RunScheduler to false on all nodes in the secondary region before enabling that region.JobSettings.RunScheduler to true on all nodes in the new primary region.JobSettings.RunScheduler to false on all nodes in the new secondary region.With the above steps complete, you have a fully functioning secondary region. You can replicate the same setup of nodes and a proxy server like the primary region. The app nodes in the secondary region won't be able to come up the first time because Mattermost will try to run some DDL statements which are not allowed with write-forwarding. So it will be stuck in a loop trying to connect. Once you fail over the region, it will start working. The primary region will still be readable, and any periodic writes will be forwarded to the secondary (now primary).
<Warning>Ensure you have separate ClusterNames for the different clusters in two regions to use the same database across 2 clusters.
To perform the failover, go to the RDS global cluster, and under Actions, select Switchover or Failover global database, and then select switchover to switch over without any data loss (which will take more time to complete). Alternatively, you can choose failover for a quicker failover at the expense of data-loss. If the entire region is unavailable anyways, then failover is no worse than switchover.
After this is done, the app nodes which were stuck trying to connect should move forward and everything should be functional. You can read/write, upload images and everything should be replicated. Everything except OpenSearch data.
ES/OS does not allow multi-writer for a single index. You can only write to 1 index at one time. Therefore, you need to perform some manual steps to reverse the replication direction, and start replicating from secondary to primary.
For simplicity, let's say site1 is primary, and site2 is secondary. Therefore, OS in site1 is the leader domain, and in site2 is the follower. The follower pulls from the leader. To switch the direction where site2 becomes leader, and site1 becomes follower.
site1 > site2 in AWS Console. This will auto-pause the replication, but the indices in site2 will still be read-only. Remove the replication rules for that.shcurl -XDELETE -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/_autofollow?pretty' -d ' { "leader_alias" : "<LEADER_ALIAS>", "name": "autofollow-rule" }'
shcurl -XPOST -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/channels/_stop?pretty' -d '{}' curl -XPOST -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/files/_stop?pretty' -d '{}' curl -XPOST -H 'Content-Type: application/json' -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_plugins/_replication/users/_stop?pretty' -d '{}'
site2 > site1 in AWS console.site1, make all the indices as followers. You must delete all indices first:shcurl -XDELETE -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/posts*?pretty' curl -XDELETE -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/channels?pretty' curl -XDELETE -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/files?pretty' curl -XDELETE -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/users?pretty'
shcurl -XPOST -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_refresh?pretty'
shcurl -s -u '<USERNAME>:<PASSWORD>' 'https://<HOSTNAME>/_cat/indices?pretty'
There's nothing you need to do to ensure the S3 bucket is auto-replicating both ways.
</Note>Once the failover has happened, and the ES/OS replication direction has been swapped, the new site can be used normally.
This becomes the final architecture:
You can use DNS to easily switch between PRIMARY to SECONDARY during a failover.
<Tip>Websockets will still point to the old data center even if you have switched DNS. You need to roll over each app node gradually to move those connections to the new data center. If all your nodes are down, no action is necessary and the clients will automatically re-connect to the new data center.
</Tip>The S3 bucket is replicated bi-directionally while the database and ES/OS is replicated uni-directionally.
When the disaster event is resolved and you are ready to restore normal operations, perform the same failover steps in reverse to return traffic to the original primary data center:
site1 and site2.JobSettings.RunScheduler on the original primary nodes and disable it on the secondary nodes.