Building a RTMP Video Streamer with NGINX Part 2 - Caching with NGINX
Building my own cache
In the previous post, I discussed how to compile NGINX with the RTMP module to enable your webcam or a static file (using FFMPEG) to stream RTMP content.
Now, let’s explore a scenario where you can enhance the stream’s reach and reduce latency by deploying small NGINX instances across a network. These instances would cache the content and serve it to clients closest to the cache. But first, let’s create a sandbox for testing this approach.
The Topology
Before we start my little experiment lets review the topology that I have put together. I have a bit of a service provider background, so I am taking some cues from that world to develop a small topology that is slightly resembling of a typical service provider network design.
In this diagram we have a couple of components.
- The Backbone Network
- BGP Autonomous System Number (ASN) 100 and OSPF Area 0.
- BGP will be configured in a full mesh. This means that every Backbone router(BBR) will peer with all other routers in ASN 100.
- The Backbone Datacenter Routers (BDR) and BBR03 and BBR04 will act as edges for ASN 100 connected to the Data Center and Region respectively.
- The Core Network
- BGP Autonomous System Number (ASN) 30 and OSPF Area 0.
- BGP will be configured in a full mesh.
- Worker nodes are placed in this network to be closest to the clients. Clients are Docker containers running linux with VLC.
- Data Center Network
- BGP Autonomous System Number (ASN) 200 and OSPF Area 0.
- iBGP between the RTR and Spine, RTRs peers with ASN 100
- DNS is hosted on the West DC while the Kubernetes master nodes are hosted only in the East DC.
- The origin video stream will be hosted in the East DC as well.
If a client wanted to connect to the video stream at the origin in the East DC it would have to traverse the network from ASN 30 through ASN 100 and into ASN 200 at the East DC and then back again. It large networks this could include a lot of hops, a lot of latency for the round trip and potentially a lot of bandwidth that would have to be built out between the client network and the DC network to handle daily peaks or even large event peaks that might happen once a year.
To deal with this we need to place the source closer to the client, that way we can avoid building large amounts of capacity between the client the origin, and we get better performance for the client because the source for the client is closer.
Using Ansible to deploy K3S
Lets assume that there are cloud providers connected in somewhat close proximity to clients who want to consume your content. We could for the period of time of the live stream deploy an NGINX container into this network for the duration of the broadcast. In my example I am going to use Ubuntu VMs as worker nodes as part of my K3S cluster. My Master nodes will reside in my East DC which will be used to control the worker nodes and deploy NGINX out to those worker nodes.
In our pretend scenario the cloud provider is using Proxmox to host their VMs. That is good news because so are we. In this playbook we use the proxmox API to deploy a set of 3 master nodes in our Datacenter and another set of worker nodes in the clouds providers East and West connectivity to the Core network.
Our Ansible inventory will look something like this, notice that because of the topology we won’t be able to have our worker nodes and masters nodes in the same subnet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
all:
children:
k3s_cluster:
children:
master:
hosts:
lab-k3s01:
ansible_host: 172.16.221.38
ansible_username: ansibleuser
vmid: 3001
storage: "ssd02-4T"
vcpus: 4
memory: 4096
data_vlan: 221
label: master
lab-k3s02:
ansible_host: 172.16.221.39
ansible_username: ansibleuser
vmid: 3002
storage: "ssd02-4T"
vcpus: 4
memory: 4096
data_vlan: 221
label: master
lab-k3s03:
ansible_host: 172.16.221.40
ansible_username: ansibleuser
vmid: 3003
storage: "ssd02-4T"
vcpus: 4
memory: 4096
data_vlan: 221
label: master
node:
hosts:
west-lab-k3s01:
ansible_host: 172.16.222.41
ansible_username: ansibleuser
vmid: 3004
storage: "ssd02-4T"
vcpus: 4
memory: 4096
data_vlan: 222
label: west-worker
east-lab-k3s01:
ansible_host: 172.16.223.42
ansible_username: ansibleuser
vmid: 3005
storage: "ssd02-4T"
vcpus: 4
memory: 4096
data_vlan: 223
label: east-worker
Because of the difference in subnets we will need to use Cilium as our Container Network Interface (CNI), Cilium allows us to not only use tunneling to connect all of the Master and Work nodes, but we can also provide specific BGP configurations for each node based on its label. Cilium also provides an Ingress controller component as well which we will use as well.
The playbook I will use to deploy the entire cluster will look something like this, I will go into a little more detail on the components of this playbook in a later post.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
---
- name: Prepare Proxmox VM Cluster
hosts: localhost
gather_facts: true
vars_files:
- vault.yml
vars_prompt:
- name: node
prompt: What Prox node do you want to deploy on?
private: false
- name: template_id
prompt: What Prox template do you want to use (Prox Template VMID)?
private: false
roles:
- role: deploy_proxmox_vm
when: prox_api is defined
- name: Create and Mount NFS share to VMs
hosts: k3s_cluster
gather_facts: true
vars_files:
- vault.yml
tasks:
- name: Install qemu-guest-agent, nfs-common, and open-iscsi
ansible.builtin.apt:
name:
- qemu-guest-agent
- nfs-common
- open-iscsi
state: present
update_cache: true
become: true
- name: Reboot the node
ansible.builtin.reboot:
become: true
when: ansible_facts['distribution'] == 'Ubuntu'
- block:
- name: Enable and start open-iscsi
ansible.builtin.systemd:
name: open-iscsi
state: started
enabled: yes
become: true
- block:
- name: Ensure mount directory exists
ansible.builtin.file:
path: /mnt/longhorn/data
state: directory
become: true
- name: Ensure NFS share is mounted
ansible.posix.mount:
path: /mnt/longhorn/data
src: "{{ nfs_mount }}"
fstype: nfs
opts: defaults
state: mounted
become: true
when: nfs_mount is defined
- block:
- name: Discover iscsi targets
command: iscsiadm -m discovery -t st -p {{ iscsi_host }}
become: true
- name: Login to iscsi target
command: iscsiadm -m node --targetname {{ hostvars[inventory_hostname]['iscsi_target'] }} --portal {{ iscsi_host }}:3260 --login
become: true
- name: Format the disk
ansible.builtin.filesystem:
fstype: ext4
dev: /dev/sdb
become: true
- name: Create directory
file:
path: /mnt/iscsi
state: directory
mode: '0755'
become: true
- name: Mount the disk
mount:
path: /mnt/iscsi
src: /dev/sdb
fstype: ext4
state: mounted
opts: _netdev
become: true
- name: Add mount to fstab
lineinfile:
path: /etc/fstab
line: '/dev/sdb /mnt/iscsi ext4 _netdev 0 0'
state: present
become: true
when: hostvars[inventory_hostname]['iscsi_target'] is defined
- name: Pre tasks
hosts: k3s_cluster
pre_tasks:
- name: Verify Ansible is version 2.11 or above. (If this fails you may need to update Ansible)
assert:
that: "ansible_version.full is version_compare('2.11', '>=')"
msg: >
"Ansible is out of date."
- name: Prepare k3s nodes
hosts: k3s_cluster
gather_facts: true
environment: "{{ proxy_env | default({}) }}"
roles:
- role: prereq
become: true
- role: download
become: true
- role: k3s_custom_registries
become: true
when: custom_registries
- name: Setup k3s servers
hosts: master
environment: "{{ proxy_env | default({}) }}"
roles:
- role: k3s_server
become: true
- name: Setup k3s agents
hosts: node
environment: "{{ proxy_env | default({}) }}"
roles:
- role: k3s_agent
become: true
- name: Configure k3s cluster
hosts: master
environment: "{{ proxy_env | default({}) }}"
roles:
- role: k3s_server_post
become: true
- name: Copy kueconfig into .kubeconfig/config
hosts: localhost
environment: "{{ proxy_env | default({}) }}"
tasks:
- name: Create .kube directory
ansible.builtin.file:
path: "{{ ansible_user_dir }}/.kube"
state: directory
- name: Copy kubeconfig into .kube/config
ansible.builtin.copy:
src: ./kubeconfig
dest: "{{ home_dir }}/.kube/config"
mode: '0600'
As part of the k3s_server_post role I have included a template that configures the Cilium BGP peers based on the node role provided in the playbook.
- In this configuration below I setup the neighbors with each router that our master, and worker nodes are directly connected to.
- At the bottom I have configured the subnet that will be advertised from the worker nodes when NGINX is deployed.
- This range will be used by the Cilium Load Balancer and will chose the next available IP in the range as Ingress configurations are added.
- We will take that IP address and configure the DNS server with an A record pointing an FQDN to the address advertised.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
apiVersion: "cilium.io/v2alpha1"
kind: CiliumBGPPeeringPolicy
metadata:
name: 01-bgp-master-peering-policy
spec:
virtualRouters:
- localASN: {{ cilium_bgp_asn }}
exportPodCIDR: {{ cilium_exportPodCIDR | default('true') }}
neighbors:
- peerAddress: '{{ cilium_master_bgp_peer_address + "/32"}}'
peerASN: {{ cilium_master_bgp_peer_asn }}
eBGPMultihopTTL: 10
connectRetryTimeSeconds: 120
holdTimeSeconds: 90
keepAliveTimeSeconds: 30
gracefulRestart:
enabled: true
restartTimeSeconds: 120
nodeSelector:
matchLabels:
master: master
---
apiVersion: "cilium.io/v2alpha1"
kind: CiliumBGPPeeringPolicy
metadata:
name: 01-bgp-west-worker-peering-policy
spec:
virtualRouters:
- localASN: {{ cilium_bgp_asn }}
exportPodCIDR: {{ cilium_exportPodCIDR | default('true') }}
podIPPoolSelector:
matchExpressions:
- {key: somekey, operator: NotIn, values: ['never-used-value']}
serviceSelector:
matchExpressions:
- {key: somekey, operator: NotIn, values: ['never-used-value']}
neighbors:
- peerAddress: '{{ cilium_west_bgp_peer_address + "/32"}}'
peerASN: {{ cilium_west_bgp_peer_asn }}
eBGPMultihopTTL: 10
connectRetryTimeSeconds: 120
holdTimeSeconds: 90
keepAliveTimeSeconds: 30
gracefulRestart:
enabled: true
restartTimeSeconds: 120
nodeSelector:
matchLabels:
worker: west-worker
---
apiVersion: "cilium.io/v2alpha1"
kind: CiliumBGPPeeringPolicy
metadata:
name: 01-bgp-east-worker-peering-policy
spec:
virtualRouters:
- localASN: {{ cilium_bgp_asn }}
exportPodCIDR: {{ cilium_exportPodCIDR | default('true') }}
podIPPoolSelector:
matchExpressions:
- {key: somekey, operator: NotIn, values: ['never-used-value']}
serviceSelector:
matchExpressions:
- {key: somekey, operator: NotIn, values: ['never-used-value']}
neighbors:
- peerAddress: '{{ cilium_east_bgp_peer_address + "/32"}}'
peerASN: {{ cilium_east_bgp_peer_asn }}
eBGPMultihopTTL: 10
connectRetryTimeSeconds: 120
holdTimeSeconds: 90
keepAliveTimeSeconds: 30
gracefulRestart:
enabled: true
restartTimeSeconds: 120
nodeSelector:
matchLabels:
worker: east-worker
---
apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
name: "01-lb-pool"
spec:
cidrs:
- cidr: "{{ cilium_bgp_lb_cidr }}"
Lets assume now that all nodes have been deployed, Cilium is setup as an Ingress controller for the cluster, now we need to build out our NGINX deployment.
Building the NGINX Custom Container
We need to create a customer container from the Latest NGINX container so that we can place inside of this custom container our customized NGINX configuration file.
Create a Github repo for the container and place these files into it
Dockerfile - This will be used by the docker build command to ensure that our nginx.conf file is placed in the correct folder on the container.
1
2
3
4
5
FROM nginx:latest
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
Makefile - Allows us to simply use the ‘'’make’’’ command to build, push or pull our container.
1
2
3
4
5
6
7
8
build:
docker build -t ghcr.io/byrn-baker/nginx-cache-docker:v0.0.1 .
push:
docker push ghcr.io/byrn-baker/nginx-cache-docker:v0.0.1
pull:
docker pull ghcr.io/byrn-baker/nginx-cache-docker:v0.0.1
nginx.conf - In this file we configure our nginx container to perform the proxy_cache function
- In the HTTP section of the configuration
- We define where our access log should be stored
- We define where the proxy cache path should be, I am encoding in both HLS and DASH so those will be separate folder locations.
- We are also defining for how long segments should be cached, how large of a file is cached, and how long an inactive segment should stay cached.
- In the Server section we define the listening port as well as the location for where our two formats can be located on this webserver.
- We also define the URL for the origin so that this NGINX server can proxy the connections from the clients to the origin.
- We define how long the requests should be cached as well as provide some headers for the client so we can tell if we are getting a cache hit or miss. Useful for troubleshooting and other stuff.
- the sub filter will ensure that the client requests are correctly rewritten when proxied so that the request makes it up to the origin and back. Not all requests will be cached, because the first client to request the stream will need to ultimately talk to the origin, this establishes the flow between the origin and this NGINX proxy cache. As more clients connect our caching efficiency increases and latency between the clients requesting the stream and receiving it should go down.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
events {}
http {
access_log /var/log/nginx/access.log;
proxy_cache_path /var/cache/nginx/HLS levels=1:2 keys_zone=HLS:10m max_size=1g inactive=10s use_temp_path=off;
proxy_cache_path /var/cache/nginx/DASH levels=1:2 keys_zone=DASH:10m max_size=1g inactive=10s use_temp_path=off;
proxy_cache_key "$scheme$request_method$host$request_uri";
server {
listen 80;
location /hls/ {
proxy_cache HLS;
proxy_pass http://origin.example.com:80/hls/;
proxy_set_header Host $host;
proxy_buffering on;
proxy_cache_valid 200 10s; # Cache HLS responses for 10 seconds
proxy_cache_valid 404 1m;
add_header X-Proxy-Cache $upstream_cache_status;
add_header X-Cache-Status $upstream_cache_status;
sub_filter 'http://origin.example.com:80/hls/' 'http://$host/hls/';
sub_filter_once off;
}
location /dash/ {
proxy_cache DASH;
proxy_pass http://origin.example.com:80/dash/;
proxy_set_header Host $host;
proxy_buffering on;
proxy_cache_valid 200 10s; # Cache DASH responses for 10 seconds
proxy_cache_valid 404 1m;
add_header X-Proxy-Cache $upstream_cache_status;
add_header X-Cache-Status $upstream_cache_status;
sub_filter 'http://origin.example.com:80/dash/' 'http://$host/dash/';
sub_filter_once off;
}
}
}
Deploying the NGINX Custom Container into Kubernetes
Now that we have our container built lets take a look at how to deploy it. I’ve created a playbook to perform this task as well. We need to create a secret in the namespace on the cluster we intend to deploy nginx, this secret if for the container repository that the customer container has been stored into (if you want a quick lesson on how to automate this further check out this post from technotim).
My playbook looks something like this, I am using vault to store secrets in, you can find more out about that here.
1
2
3
4
5
6
7
8
9
---
# usage ansible-playbook pb.install-nginx-cache.yml --ask-vault-pass --tags="install"
- name: Install Nginx-Cache on k3s
hosts: localhost
vars_files:
- vault.yml
roles:
- role: install_nginx_cache
The install_nginx_cache role looks something like this.
- Creating the docker register secret - this provide the cluster a way to pull down the container from the Github container registry where I have stored my customer container.
- Create a Deployment - The task simply creates a name for this deployment, places it in the default namespace, the annotations tell it to use Cilium for ingress.
- I want to have 3 replicas running at all times.
- Under spec I have defined that this is a container and from where it should be pulled.
- I have also include a mount path for the container, this will be used by the container to store the cache segments from the origin.
- You can build these paths directly to RAM for quicker access if you want as well. This is defined near the bottom by the key value pair of medium.
- We create a service that can be exposed externally or mapped by an ingress
- The ingress rules will route traffic to this container based on the clients host name in the clients request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
- block:
- name: Create a docker registry secret
community.kubernetes.k8s:
state: present
definition:
apiVersion: v1
kind: Secret
metadata:
name: ghcr-io-creds
namespace: default
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: "{{ docker_config | to_json | b64encode }}"
vars:
docker_config:
auths:
ghcr.io/byrn-baker/nginx-cache-docker:
username: "{{ vault_docker_username }}"
password: "{{ vault_docker_password }}"
auth: "{{ (vault_docker_username + ':' + vault_docker_password) | b64encode }}"
- name: Create a deployment
community.kubernetes.k8s:
definition:
kind: Deployment
apiVersion: apps/v1
metadata:
name: nginx-cache
namespace: default
labels:
app: nginx-cache
annotations:
io.cilium.proxy-visibility: "<Ingress/80/TCP/HTTP>"
spec:
replicas: 3
progressDeadlineSeconds: 600
revisionHistoryLimit: 2
strategy:
type: Recreate
selector:
matchLabels:
app: nginx-cache
template:
metadata:
labels:
app: nginx-cache
spec:
containers:
- name: nginx-cache
image: ghcr.io/example/nginx-cache-docker:v0.0.1
imagePullPolicy: Always
volumeMounts:
- mountPath: /var/cache/nginx
name: cache-volume
imagePullSecrets:
- name: ghcr-io-creds
volumes:
- name: cache-volume
emptyDir:
medium: Memory
state: present
- name: Create a service for nginx-cache
community.kubernetes.k8s:
definition:
kind: Service
apiVersion: v1
metadata:
name: nginx-cache
namespace: default
spec:
selector:
app: nginx-cache
ports:
- protocol: TCP
port: 80
targetPort: 80
state: present
- name: Create a cilium Ingress for NGINX
community.kubernetes.k8s:
definition:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
namespace: default
spec:
ingressClassName: cilium
rules:
- host: "live.example.com"
http:
paths:
- backend:
service:
name: nginx-cache
port:
number: 80
path: /
pathType: Prefix
state: present
tags: install
Checking connectivity
If all goes well we should see outputs like the below. This tells us that the pods are running, our ingress is configured, and we have a service created for the deployment that has been assigned an external IP that matches our ingress.
1
2
3
4
5
$ k get pods
NAME READY STATUS RESTARTS AGE
nginx-cache-75d55cb78-psk6b 1/1 Running 0 6h3m
nginx-cache-75d55cb78-qsvh8 1/1 Running 0 6h3m
nginx-cache-75d55cb78-xtfg9 1/1 Running 0 6h3m
1
2
3
$ k get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx-ingress cilium live.lab.video-ops-lab.com 172.16.228.2 80 6h59m
1
2
3
4
5
$ k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cilium-ingress-nginx-ingress LoadBalancer 10.43.250.206 172.16.228.2 80:32519/TCP,443:31901/TCP 6h59m
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 7h52m
nginx-cache ClusterIP 10.43.219.152 <none> 80/TCP 6h59m
While we are checking things lets have a look at some routers output as well. ASN 64513 is the ASN assigned to the Kubernetes cluster.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
region-router01#sh ip bgp sum
BGP router identifier 172.16.30.128, local AS number 30
BGP table version is 23, main routing table version 23
13 network entries using 1820 bytes of memory
20 path entries using 1600 bytes of memory
8/5 BGP path/bestpath attribute entries using 1152 bytes of memory
3 BGP AS-PATH entries using 72 bytes of memory
0 BGP route-map cache entries using 0 bytes of memory
0 BGP filter-list cache entries using 0 bytes of memory
BGP using 4644 total bytes of memory
BGP activity 104/75 prefixes, 232/191 paths, scan interval 60 secs
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
172.16.30.129 4 30 484 482 23 0 0 07:09:38 8
172.16.30.130 4 30 478 485 23 0 0 07:09:37 1
172.16.30.131 4 30 476 481 23 0 0 07:09:38 3
172.16.100.12 4 100 481 483 23 0 0 07:09:37 4
172.16.222.41 4 64513 863 951 23 0 0 07:09:26 3
1
2
3
4
5
6
7
8
9
10
11
region-router01#sh ip bgp
BGP table version is 23, local router ID is 172.16.30.128
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
r RIB-failure, S Stale, m multipath, b backup-path, f RT-Filter,
x best-external, a additional-path, c RIB-compressed,
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found
Network Next Hop Metric LocPrf Weight Path
* i 172.16.228.3/32 172.16.30.129 0 100 0 64513 i
*> 172.16.222.41 0 64513 i
Notice that we have two next hops for 172.16.228.3/32, that is because both workers are currently advertising the same route, this is good because both workers are also running the NGINX container as well.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
region-router02#sh ip bgp sum
BGP router identifier 172.16.30.129, local AS number 30
BGP table version is 29, main routing table version 29
13 network entries using 1820 bytes of memory
20 path entries using 1600 bytes of memory
8/5 BGP path/bestpath attribute entries using 1152 bytes of memory
3 BGP AS-PATH entries using 72 bytes of memory
0 BGP route-map cache entries using 0 bytes of memory
0 BGP filter-list cache entries using 0 bytes of memory
BGP using 4644 total bytes of memory
BGP activity 79/50 prefixes, 199/158 paths, scan interval 60 secs
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
172.16.30.128 4 30 485 486 29 0 0 07:12:18 8
172.16.30.130 4 30 480 485 29 0 0 07:12:18 1
172.16.30.131 4 30 477 485 29 0 0 07:12:18 3
172.16.100.14 4 100 491 488 29 0 0 07:12:18 4
172.16.223.42 4 64513 869 953 29 0 0 07:12:04 3
1
2
3
4
5
6
7
8
9
10
11
region-router02#sh ip bgp
BGP table version is 29, local router ID is 172.16.30.129
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
r RIB-failure, S Stale, m multipath, b backup-path, f RT-Filter,
x best-external, a additional-path, c RIB-compressed,
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found
Network Next Hop Metric LocPrf Weight Path
* i 172.16.228.2/32 172.16.30.128 0 100 0 64513 i
*> 172.16.223.42 0 64513 i
Lets also just check the BGP table on our market routers as well. Our clients are directly connected to these routers so they will need to be able to communicate with the worker nodes.
1
2
3
4
5
6
7
8
9
10
11
market-router01#sh ip bgp
BGP table version is 113, local router ID is 172.16.30.130
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
r RIB-failure, S Stale, m multipath, b backup-path, f RT-Filter,
x best-external, a additional-path, c RIB-compressed,
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found
Network Next Hop Metric LocPrf Weight Path
*>i 172.16.228.2/32 172.16.30.128 0 100 0 64513 i
* i 172.16.30.129 0 100 0 64513 i
1
2
3
4
5
6
7
8
9
10
11
market-router02#sh ip bgp
BGP table version is 24, local router ID is 172.16.30.131
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
r RIB-failure, S Stale, m multipath, b backup-path, f RT-Filter,
x best-external, a additional-path, c RIB-compressed,
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found
Network Next Hop Metric LocPrf Weight Path
*>i 172.16.228.2/32 172.16.30.128 0 100 0 64513 i
* i 172.16.30.129 0 100 0 64513 i
Lets check a traceroute from Docker18 and then from Docker20. The ingress controller does not respond to pings, so our traceroute looks a little funky, but we seeing the gateway in the traceroute followed by a *. I will take that as at least the path exists to the workers.
Lets see if we can now load the stream in VLC
The stream is loading on both clients, they are not totally synced, but I do allow for segments to live in the cache for a couple of seconds, the hls-m3u8 contains a list of files (manifest) that it should be request in order, so depending on when a client joins and what files are listed in that manifest it will request the first file in the list.
Lets take a look at a wireshark capture as well. In the capture you see that we have several HTTP requests, I have highlighted one of the 200 responses here.
- In the Hypertext Transfer Protocol can see that our x-proxy-cache and x-proxy-status headers are reported back that these are cache hits as well as the media type at the bottom which is currently HLS.
There is a high level look at how you might cache a live stream where you origin and your clients are separated by more than one network, and how to use NGINX as a proxy cache. The concept can be useful for more than just video.