Deploying a MistServer CDN with Ansible
Setting up a MistServer CDN
The easiest and most common deployment of a MistServer CDN would be setting up multiple MistServers, pointing them to a load balancer and sharing the available live streams through pull ingest. This might sound daunting, but the set up is much easier than you'd expect if you leverage wildcard streams, JWT or streamkeys.
Almost 90% of the set up can be done through one smart configuration file, which means that new servers can be fully up and running in mere moments.
Ansible for easier deployment
If you have a deploy tool we recommend using what you're familiar with, should you have nothing and are looking for something we'd recommend Ansible. It's served us well for multiple years and has the bonus of showing what changes have been made to what servers in an easy to understand overview.
Ansible itself works fairly easy, essentially there's a list of servers and a list of instructions. The combination is what allows for easy and fast deployment.
You will need the following:
- Ansible
- An list of servers (
inventory.yml) - A list of instructions (
instructions.yml) - Additional configuration files to use with the instructions (
mistserver.conf.j2) - Optionally: Any other scripts/files to add.
Basic command:
ansible-playbook -i inventory.yml instrutions.yml
Dry run command:
ansible-playbook -i inventory.yml instrutions.yml --check
When making changes we recommend running the ansible-playbook with --check, instead of actually applying anything it will tell you what it would have done. This is a great method to double-check you'll get the result you want before deploying anything.
Ansible for Ubuntu24
instructions.yml
---
- name: Check OS
hosts: all
become: true
vars:
config_source: "./mistserver.conf.j2"
config_destination: "/etc/mistserver.conf"
# Map Ansible architecture facts to MistServer download slugs
arch_map:
x86_64: "64"
amd64: "64"
aarch64: "aarch64"
armv7l: "armv7"
pre_tasks:
- name: Get latest MistServer version from GitHub
uri:
url: "https://api.github.com/repos/DDVTECH/mistserver/releases/latest"
return_content: true
headers:
Accept: "application/vnd.github.v3+json"
register: github_response
delegate_to: localhost
run_once: true
check_mode: no
become: false
- name: Set global version fact
set_fact:
latest_ver: "{{ (github_response.content | from_json).tag_name | replace('v', '') }}"
delegate_to: localhost
run_once: true
become: false
tasks:
## 1. ARCHITECTURE & VERSION CHECK
- name: Set download suffix based on architecture
set_fact:
mist_arch: "{{ arch_map[ansible_architecture] | default('64') }}"
check_mode: no
- name: Report Architecture Detection
debug:
msg:
- "Host Architecture: {{ ansible_architecture }}"
- "Mapped MistServer Suffix: {{ mist_arch }}"
- "Target URL: https://releases.mistserver.org/is/mistserver_{{ mist_arch }}Vlatest.tar.gz"
- name: Check if MistController exists
stat:
path: /usr/bin/MistController
register: mist_binary
- name: Get installed version
shell: "MistController --version | head -n 1 | awk '{print $2}' | tr -d ','"
register: installed_ver_cmd
when: mist_binary.stat.exists
changed_when: false
failed_when: false
check_mode: no
- name: Determine if Update or Install is needed
set_fact:
needs_install: "{{ not mist_binary.stat.exists or (installed_ver_cmd.stdout | trim != latest_ver | trim) }}"
## 2. DYNAMIC INSTALLATION
- name: Download and Install/Update MistServer for {{ ansible_architecture }}
shell: "curl -o - https://releases.mistserver.org/is/mistserver_{{ mist_arch }}Vlatest.tar.gz 2>/dev/null | sh"
when: needs_install
notify: Restart MistServer
## 3. SHARED MEMORY OPTIMIZATION
- name: Configure /dev/shm to 95% of RAM
lineinfile:
path: /etc/fstab
regexp: '^tmpfs\s+/dev/shm\s+tmpfs\s+'
line: "tmpfs /dev/shm tmpfs defaults,size=95% 0 0"
state: present
register: shm_conf
- name: Remount shared memory
command: mount -o remount /dev/shm
when: shm_conf.changed and not ansible_check_mode
## 4. CONFIGURATION MANAGEMENT
- name: Deploy MistServer configuration file
template:
src: "{{ config_source }}"
dest: "{{ config_destination }}"
owner: root
group: root
mode: '0644'
inventory.yml
server_01.example friendly_name="Server 01"
server_02.example friendly_name="Server 02"
server_03.example friendly_name="Server 03"
server_04.example friendly_name="Server 04"
server_05.example friendly_name="Server 05"
server_06.example friendly_name="Server 06"
mistserver.conf.j2
{"account":{"example":{"password":"1a79a4d60de6718e8e5b326e338ae533"}},"auto_push":null,"bandwidth":{"exceptions":["::1","127.0.0.0/8","10.0.0.0/8","192.168.0.0/16","172.16.0.0/12"],"limit":},"config":{"accesslog":"LOG","controller":{"interface":null,"port":null,"username":null},"debug":null,"defaultStream":null,"location":{"lat":0,"lon":0,"name":""},"prometheus":"example_metrics","protocols":[{"connector":"AAC"},{"connector":"CMAF"},{"connector":"DTSC"},{"connector":"EBML"},{"connector":"FLAC"},{"connector":"FLV"},{"connector":"H264"},{"connector":"HDS"},{"connector":"HLS","nonchunked": true},{"connector":"HTTP"},{"connector":"HTTPTS"},{"connector":"JPG"},{"connector":"JSON"},{"connector":"MP3"},{"connector":"MP4"},{"connector":"OGG"},{"connector":"RTMP"},{"connector":"RTSP"},{"connector":"SDP"},{"connector":"SubRip"},{"connector":"TSSRT"},{"connector":"WAV"},{"connector":"WebRTC"}],"serverid":"{{ friendly_name }}","sessionAccTimeout":35,"sessionInputMode":15,"sessionOutputMode":15,"sessionRejTimeout":30,"sessionStreamInfoMode":"3","sessionUnspecifiedMode":0,"sessionViewerMode":14,"tknMode":15,"triggers":{},"trustedproxy":[]},"extwriters":[],"jwks":[],"push_settings":{"maxspeed":0,"wait":3},"streamkeys":{},"streams":{"example":{"name":"example","processes":[],"source":"balance:http://example:8042?fallback=push://","stop_sessions":false,"tags":[]}},"tags":[],"ui_settings":{"sort_autopushes":{"by":"Stream","dir":1},"sort_pushes":{"by":"Statistics","dir":1},"sortstreams":{"by":"name","dir":1}},"variables":null}
The above configuration will:
- Set the defaults for protocol inputs
- Set up an account (
example:example) - Set up prometheus metrics
example_metrics - Set up Stream
examplewith a load balance setup
Now we would recommend taking this configuration & editing what you want changed and then applying it into your setup. Do keep in mind that the "serverid":"{{ friendly_name }}" is invalid JSON, but needed for Ansible to automatically fill in the Server name when applying the configuration. Be sure to remove this and add it again if you're using this configuration directly!
Next steps
Now as always with set ups like these, we would recommend making adjustments to fit your needs, or even just start from scratch if you'd rather do a completely different order of things. Of course it helps to read up on how to use Ansible before making too many adjustments.
If you want to use the example we listed above, just be sure to change the config to your intended config, add SSL and you should be ready for most basic deployments.
Optional handy Ansible settings
Optional settings such as adding a script for MistServer to use or blocking certain IP ranges through iptables.
Optional settings
- name: Copy script.sh to /root/ #change tha path to your liking
ansible.builtin.copy:
src: ./script.sh
dest: /root/script.sh
owner: root
group: root
mode: '0755' # Makes the script executable for usage
- name: Block malicious IP ranges
ansible.builtin.iptables:
chain: INPUT
source: "{{ item }}"
jump: DROP
action: insert
loop:
- 123.456.789.123/24 #change/add addresses here
- 456.789.123.456/24 #any extra like this
Be sure to link the source of the scripts correctly if they're not in the same directory.
You could even add Nginx into the mix.