├── .gitignore ├── LICENSE ├── README.md ├── alpine-fat └── Dockerfile └── nginx.conf /.gitignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | !auto-reload-nginx.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thong Duy Le 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | `k8s-openresty-streaming` is a full-fledged media streaming server with [OpenResty][1] and [rtmp module][3] for [Kubernetes][6] 3 | 4 | # Features 5 | * Support RTMP, HLS and MPEG-DASH streaming (thanks to [nginx rtmp module][3]) 6 | * On the fly (and free) SSL registration and renewal inside [OpenResty/nginx][1] with [Let's Encrypt][7] (thanks to [lua-resty-auto-ssl][4]) 7 | * Integrated GeoIP REST Api (thanks to [Telize][2]) 8 | * Lightweight image based on [openresty/openresty:alpine-fat flavor][5] 9 | 10 | # Prerequisites 11 | * [Git][8] 12 | * [Docker][9], required for single server deployment only. 13 | * [Kubernetes (aka K8s)][6], required for K8s cluster deployment. 14 | 15 | # Usage 16 | ### Quickstart with Docker 17 | SSH to your server and run 18 | ```bash 19 | # Clone this repo 20 | git clone https://github.com/duythongle/k8s-openresty-streaming.git 21 | # Run image and mount config files for later editing 22 | docker run -dit --name my_streaming_server \ 23 | -p 80:80 \ 24 | -p 443:443 \ 25 | -p 1935:1935 \ 26 | -v ~/k8s-openresty-streaming/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf \ 27 | thongld/k8s-openresty-streaming:alpine-fat \ 28 | openresty -g "daemon off;" 29 | ``` 30 | Then your browser should display OpenResty welcome home page at http://*streaming_server_ip*/ . Later on, just edit the mounted nginx.config file at `~/k8s-openresty-streaming/nginx.conf` for your needs and apply changes with command below 31 | ```bash 32 | sudo docker exec my_streaming_server sh -c "openresty -t && openresty -s reload" 33 | ``` 34 | ### Quickstart with Kubernetes 35 | 36 | ```bash 37 | # Coming soon... 38 | ``` 39 | Then go to http://streaming_server_ip/ to view openresty welcome home page 40 | ### More... 41 | * GeoIP REST API can be accessed at public location `/api/geoip`. Ex.: http://*streaming_server_ip*/api/geoip/location/8.8.4.4. 42 | Read more geoip api at [Telize GeoIP REST API][2] 43 | * Point your DNS to the server ip and see the magic of Auto SSL happens at https://*streaming_server_domain* 44 | > Note: Let's Encrypt has [rate limits][10] and the first https request for a domain may take a few seconds to complete 45 | * [nginx rtmp module][3] has default application endpoint `my_live_stream`. You can push your live stream to the server via url: 46 | rtmp://*streaming_server_ip_or_domain*:1935/*my_live_stream*/*my_stream_name* and playback with hls url http://*streaming_server_ip_or_domain*/hls/*my_stream_name* 47 | 48 | # Building from source 49 | ```bash 50 | git clone https://github.com/duythongle/k8s-openresty-streaming.git 51 | cd k8s-openresty-streaming 52 | docker build -t openresty-streaming-server -f alpine-fat/Dockerfile . 53 | # Then run the image 54 | docker run -dit --name my_streaming_server \ 55 | -p 80:80 \ 56 | -p 443:443 \ 57 | -p 1935:1935 \ 58 | -v ~/k8s-openresty-streaming/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf \ 59 | openresty-streaming-server \ 60 | openresty -g "daemon off;" 61 | ``` 62 | 63 | # TODO 64 | * 12factor ready 65 | * K8s ready 66 | * Helm support 67 | * Auto reload Nginx when .conf changes 68 | 69 | [1]: https://github.com/openresty/openresty 70 | [2]: https://github.com/fcambus/telize 71 | [3]: https://github.com/arut/nginx-rtmp-module 72 | [4]: https://github.com/GUI/lua-resty-auto-ssl 73 | [5]: https://github.com/openresty/docker-openresty/blob/master/alpine-fat/Dockerfile 74 | [6]: https://kubernetes.io/ 75 | [7]: https://letsencrypt.org 76 | [8]: https://git-scm.com/ 77 | [9]: https://docker.io 78 | [10]:https://letsencrypt.org/docs/rate-limits/ 79 | -------------------------------------------------------------------------------- /alpine-fat/Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile - alpine-fat 2 | # https://github.com/duythongle/k8s-openresty-streaming 3 | # 4 | # This is an alpine-based build based on 5 | # https://github.com/openresty/docker-openresty/tree/master/alpine-fat 6 | 7 | FROM alpine:3.7 8 | 9 | LABEL maintainer="Tom Le " 10 | 11 | # Docker Build Arguments 12 | ARG RESTY_VERSION="1.13.6.1" 13 | ARG RESTY_LUAROCKS_VERSION="2.4.3" 14 | ARG RESTY_OPENSSL_VERSION="1.0.2k" 15 | ARG RESTY_PCRE_VERSION="8.41" 16 | ARG RESTY_J="1" 17 | ARG TELIZE_VERSION="2.0.0" 18 | ARG GEOIP2_VERSION="2.0" 19 | ARG RTMP_VERSION="1.2.1" 20 | ARG RESTY_CONFIG_OPTIONS="\ 21 | --with-file-aio \ 22 | --with-http_addition_module \ 23 | --with-http_auth_request_module \ 24 | --with-http_dav_module \ 25 | --with-http_flv_module \ 26 | --with-http_gunzip_module \ 27 | --with-http_gzip_static_module \ 28 | --with-http_image_filter_module=dynamic \ 29 | --with-http_geoip_module=dynamic \ 30 | --with-http_mp4_module \ 31 | --with-http_random_index_module \ 32 | --with-http_realip_module \ 33 | --with-http_secure_link_module \ 34 | --with-http_slice_module \ 35 | --with-http_ssl_module \ 36 | --with-http_stub_status_module \ 37 | --with-http_sub_module \ 38 | --with-http_v2_module \ 39 | --with-http_xslt_module=dynamic \ 40 | --with-ipv6 \ 41 | --with-mail \ 42 | --with-mail_ssl_module \ 43 | --with-md5-asm \ 44 | --with-pcre-jit \ 45 | --with-sha1-asm \ 46 | --with-stream \ 47 | --with-stream_ssl_module \ 48 | --with-threads \ 49 | --add-dynamic-module=/tmp/geoip2-${GEOIP2_VERSION} \ 50 | --add-dynamic-module=/tmp/rtmp-${RTMP_VERSION}" 51 | ARG RESTY_CONFIG_OPTIONS_MORE="" 52 | 53 | # These are not intended to be user-specified 54 | ARG _RESTY_CONFIG_DEPS="--with-openssl=/tmp/openssl-${RESTY_OPENSSL_VERSION} --with-pcre=/tmp/pcre-${RESTY_PCRE_VERSION}" 55 | 56 | 57 | # 1) Install apk dependencies 58 | # 2) Download and untar OpenSSL, PCRE, and OpenResty 59 | # 3) Build OpenResty 60 | # 4) Cleanup 61 | 62 | RUN apk add --no-cache --virtual .build-deps \ 63 | curl \ 64 | gd-dev \ 65 | geoip-dev \ 66 | libxslt-dev \ 67 | perl-dev \ 68 | readline-dev \ 69 | zlib-dev \ 70 | libmaxminddb-dev \ 71 | && apk add --no-cache \ 72 | libmaxminddb \ 73 | openssl \ 74 | sudo \ 75 | bash \ 76 | build-base \ 77 | curl \ 78 | gd \ 79 | geoip \ 80 | libgcc \ 81 | libxslt \ 82 | linux-headers \ 83 | make \ 84 | perl \ 85 | unzip \ 86 | zlib \ 87 | && mkdir -p /tmp/geoip2-${GEOIP2_VERSION} \ 88 | && curl -fSL https://github.com/leev/ngx_http_geoip2_module/archive/${GEOIP2_VERSION}.tar.gz | \ 89 | tar xzf - -C /tmp/geoip2-${GEOIP2_VERSION} --strip-components=1 \ 90 | && mkdir -p /tmp/rtmp-${RTMP_VERSION} \ 91 | && curl -fSL https://github.com/arut/nginx-rtmp-module/archive/v${RTMP_VERSION}.tar.gz | \ 92 | tar xzf - -C /tmp/rtmp-${RTMP_VERSION} --strip-components=1 \ 93 | && cd /tmp \ 94 | && curl -fSL https://www.openssl.org/source/openssl-${RESTY_OPENSSL_VERSION}.tar.gz -o openssl-${RESTY_OPENSSL_VERSION}.tar.gz \ 95 | && tar xzf openssl-${RESTY_OPENSSL_VERSION}.tar.gz \ 96 | && curl -fSL https://ftp.pcre.org/pub/pcre/pcre-${RESTY_PCRE_VERSION}.tar.gz -o pcre-${RESTY_PCRE_VERSION}.tar.gz \ 97 | && tar xzf pcre-${RESTY_PCRE_VERSION}.tar.gz \ 98 | && curl -fSL https://openresty.org/download/openresty-${RESTY_VERSION}.tar.gz -o openresty-${RESTY_VERSION}.tar.gz \ 99 | && tar xzf openresty-${RESTY_VERSION}.tar.gz \ 100 | && cd /tmp/openresty-${RESTY_VERSION} \ 101 | && ./configure -j${RESTY_J} ${_RESTY_CONFIG_DEPS} ${RESTY_CONFIG_OPTIONS} ${RESTY_CONFIG_OPTIONS_MORE} \ 102 | && make -j${RESTY_J} \ 103 | && make -j${RESTY_J} install \ 104 | && cd /tmp \ 105 | && rm -rf \ 106 | openssl-${RESTY_OPENSSL_VERSION} \ 107 | openssl-${RESTY_OPENSSL_VERSION}.tar.gz \ 108 | openresty-${RESTY_VERSION}.tar.gz openresty-${RESTY_VERSION} \ 109 | pcre-${RESTY_PCRE_VERSION}.tar.gz pcre-${RESTY_PCRE_VERSION} \ 110 | && echo curl -fSL https://github.com/luarocks/luarocks/archive/${RESTY_LUAROCKS_VERSION}.tar.gz -o luarocks-${RESTY_LUAROCKS_VERSION}.tar.gz \ 111 | && curl -fSL https://github.com/luarocks/luarocks/archive/${RESTY_LUAROCKS_VERSION}.tar.gz -o luarocks-${RESTY_LUAROCKS_VERSION}.tar.gz \ 112 | && tar xzf luarocks-${RESTY_LUAROCKS_VERSION}.tar.gz \ 113 | && cd luarocks-${RESTY_LUAROCKS_VERSION} \ 114 | && ./configure \ 115 | --prefix=/usr/local/openresty/luajit \ 116 | --with-lua=/usr/local/openresty/luajit \ 117 | --lua-suffix=jit-2.1.0-beta3 \ 118 | --with-lua-include=/usr/local/openresty/luajit/include/luajit-2.1 \ 119 | && make build \ 120 | && make install \ 121 | && cd /tmp \ 122 | && rm -rf luarocks-${RESTY_LUAROCKS_VERSION} luarocks-${RESTY_LUAROCKS_VERSION}.tar.gz \ 123 | && apk add --no-cache --virtual .gettext gettext \ 124 | && mv /usr/bin/envsubst /tmp/ \ 125 | && runDeps="$( \ 126 | scanelf --needed --nobanner /tmp/envsubst \ 127 | | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \ 128 | | sort -u \ 129 | | xargs -r apk info --installed \ 130 | | sort -u \ 131 | )" \ 132 | && apk add --no-cache --virtual $runDeps \ 133 | && mv /tmp/envsubst /usr/local/bin/ \ 134 | && ln -sf /dev/stdout /usr/local/openresty/nginx/logs/access.log \ 135 | && ln -sf /dev/stderr /usr/local/openresty/nginx/logs/error.log 136 | 137 | RUN sudo -H /usr/local/openresty/luajit/bin/luarocks install lua-resty-http \ 138 | && sudo -H /usr/local/openresty/luajit/bin/luarocks install lua-resty-auto-ssl \ 139 | && sudo -H /usr/local/openresty/luajit/bin/luarocks install lua-iconv 140 | 141 | RUN openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ 142 | -subj '/CN=sni-support-required-for-valid-ssl' \ 143 | -keyout /etc/ssl/resty-auto-ssl-fallback.key \ 144 | -out /etc/ssl/resty-auto-ssl-fallback.crt 145 | 146 | ADD https://github.com/duythongle/k8s-openresty-streaming/raw/master/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf 147 | #ADD conf.d /usr/local/openresty/nginx/conf/conf.d 148 | 149 | RUN mkdir -p /tmp/telize-v${TELIZE_VERSION} \ 150 | && curl -fSL https://github.com/fcambus/telize/archive/${TELIZE_VERSION}.tar.gz | \ 151 | tar xzf - -C /tmp/telize-v${TELIZE_VERSION} --strip-components=1 \ 152 | && cp /tmp/telize-v${TELIZE_VERSION}/country-code3.conf /usr/local/openresty/nginx/conf \ 153 | && cp /tmp/telize-v${TELIZE_VERSION}/timezone-offset.conf /usr/local/openresty/nginx/conf \ 154 | && cp /tmp/telize-v${TELIZE_VERSION}/telize.conf /usr/local/openresty/nginx/conf 155 | 156 | RUN mkdir -p /var/db/GeoIP \ 157 | && curl -fSL https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz | \ 158 | tar xzf - -C /var/db/GeoIP --strip-components=1 \ 159 | && curl -fSL https://geolite.maxmind.com/download/geoip/database/GeoLite2-ASN.tar.gz | \ 160 | tar xzf - -C /var/db/GeoIP --strip-components=1 161 | 162 | RUN rm -rf /tmp/* && apk del .build-deps .gettext 163 | 164 | ENV PATH=$PATH:/usr/local/openresty/luajit/bin:/usr/local/openresty/nginx/sbin:/usr/local/openresty/bin 165 | CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"] -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | load_module modules/ngx_http_geoip2_module.so; 2 | load_module modules/ngx_rtmp_module.so; 3 | 4 | worker_processes auto; 5 | 6 | events { 7 | worker_connections 1024; 8 | multi_accept on; 9 | } 10 | 11 | http { 12 | include mime.types; 13 | default_type application/octet-stream; 14 | 15 | charset UTF-8; 16 | charset_types text/xml text/plain text/vnd.wap.wml application/javascript application/rss+xml application/json application/vnd.apple.mpegurl; 17 | keepalive_timeout 65; 18 | 19 | map_hash_max_size 8192; 20 | map_hash_bucket_size 64; 21 | 22 | ssl_session_cache shared:SSL:20m; 23 | ssl_session_timeout 10m; 24 | ssl_prefer_server_ciphers on; 25 | ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; 26 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 27 | 28 | include timezone-offset.conf; 29 | include country-code3.conf; 30 | geoip2 /var/db/GeoIP/GeoLite2-City.mmdb { 31 | $geoip2_continent_code continent code; 32 | $geoip2_country country names en; 33 | $geoip2_country_code country iso_code; 34 | $geoip2_region subdivisions 0 names en; 35 | $geoip2_region_code subdivisions 0 iso_code; 36 | $geoip2_city city names en; 37 | $geoip2_postal_code postal code; 38 | $geoip2_latitude location latitude; 39 | $geoip2_longitude location longitude; 40 | $geoip2_timezone location time_zone; 41 | } 42 | 43 | geoip2 /var/db/GeoIP/GeoLite2-ASN.mmdb { 44 | $geoip2_asn autonomous_system_number; 45 | $geoip2_organization autonomous_system_organization; 46 | } 47 | 48 | # The "auto_ssl" shared dict must be defined with enough storage space to 49 | # hold your certificate data. 50 | lua_shared_dict auto_ssl 1m; 51 | lua_shared_dict auto_ssl_settings 64k; 52 | 53 | # A DNS resolver must be defined for OSCP stapling to function. 54 | resolver 8.8.8.8; 55 | 56 | # Initial setup tasks. 57 | init_by_lua_block { 58 | redis = require "resty.redis" 59 | cjson = require "cjson" 60 | auto_ssl = (require "resty.auto-ssl").new() 61 | 62 | -- Define a function to determine which SNI domains to automatically handle 63 | -- and register new certificates for. Defaults to not allowing any domains, 64 | -- so this must be configured. 65 | auto_ssl:set("allow_domain", function(domain) 66 | return true 67 | end) 68 | auto_ssl:set("dir", "/tmp") 69 | 70 | auto_ssl:init() 71 | } 72 | 73 | init_worker_by_lua_block { 74 | auto_ssl:init_worker() 75 | } 76 | 77 | # Internal server running on port 8999 for handling certificate tasks. 78 | server { 79 | listen 127.0.0.1:8999; 80 | client_body_buffer_size 128k; 81 | client_max_body_size 128k; 82 | location / { 83 | content_by_lua_block { 84 | auto_ssl:hook_server() 85 | } 86 | } 87 | } 88 | 89 | # HTTP(S) server 90 | server { 91 | listen 80 default_server; 92 | listen 443 ssl; 93 | 94 | # Endpoint used for performing domain verification with Let's Encrypt. 95 | location /.well-known/acme-challenge/ { 96 | content_by_lua_block { 97 | auto_ssl:challenge_server() 98 | } 99 | } 100 | 101 | # Dynamic handler for issuing or returning certs for SNI domains. 102 | ssl_certificate_by_lua_block { 103 | auto_ssl:ssl_certificate() 104 | } 105 | 106 | # You must still define a static ssl_certificate file for nginx to start. 107 | # 108 | # You may generate a self-signed fallback with: 109 | # 110 | # openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ 111 | # -subj '/CN=sni-support-required-for-valid-ssl' \ 112 | # -keyout /etc/ssl/resty-auto-ssl-fallback.key \ 113 | # -out /etc/ssl/resty-auto-ssl-fallback.crt 114 | ssl_certificate /etc/ssl/resty-auto-ssl-fallback.crt; 115 | ssl_certificate_key /etc/ssl/resty-auto-ssl-fallback.key; 116 | 117 | location /api/geoip { 118 | proxy_pass http://127.0.0.1; 119 | } 120 | 121 | location /hls { 122 | # Serve HLS fragments 123 | types { 124 | application/vnd.apple.mpegurl m3u8; 125 | video/mp2t ts; 126 | } 127 | root /tmp; 128 | add_header Cache-Control no-cache; 129 | } 130 | 131 | location /dash { 132 | # Serve DASH fragments 133 | root /tmp; 134 | add_header Cache-Control no-cache; 135 | } 136 | } 137 | 138 | include telize.conf; 139 | include conf.d/*.conf; 140 | } 141 | 142 | rtmp { 143 | server { 144 | listen 1935; 145 | chunk_size 4000; 146 | 147 | application my_live_stream { 148 | # RTMP Streaming 149 | live on; 150 | 151 | # HLS Streaming 152 | hls on; 153 | hls_path /tmp/hls; 154 | 155 | # MPEG-DASH Streaming, similar to HLS 156 | dash on; 157 | dash_path /tmp/dash; 158 | } 159 | } 160 | } --------------------------------------------------------------------------------