docker部署ffplayout全家桶

发布于 2022-04-10  759 次阅读


折腾我好久,最后发现是官方的dockerfile不好用。

ffplayout是什么?全家桶又是什么?

ffplayout主页
ffplayout是一个24/7在线推流的工具,借助ffplayout可以实现24小时直播,比如音乐电台之类。

主要的功能由ffplayout_engine实现,其实原理上就是一个python程序,控制两个ffmpeg,一个对原始媒体解码,另一个编码并输出。目前ffplayout更新到v4.0.0,根据github页面的说明,以后会从python迁移到rust,性能上应该可以有所提升。

全家桶则在engine的基础上加入了ffplayout-frontendffplayout-api,engine本身就可以实现核心的功能,但是修改配置、播放列表等需要直接修改文件。通过frontend和api可以从网页端直接控制engine,方便很多。

官方推荐的rtmp服务器是SRS,但我已经有owncast,就不另外部署了。

部署方法

官方提供了安装脚本,每一个组件repo里也都有对应的手动安装方法,不过我更希望可以用docker部署。

官方虽然提供了docker部署方法,但是这个repo基本没有更新,说明也不全,没办法拿来就用,所以我记录一下docker部署的方法。

虽说官方的docker部署repo不能直接用,作为一个starting point还是很适合的,用git拉取到本地,更新/修改一些内容后就可以成功部署。

拉取代码到本地后,先按需要修改docker-compose.yml,比如注释掉image,去掉SRS等,另外我因为一开始部署总是不成功,添加了一些log相关的路径。

---
version: '3.6'

services:
#    srs:
#        container_name: srs
#        image: ossrs/srs:3
#        volumes:
#            - "./config/srs.conf:/usr/local/srs/conf/srs.conf"
#            - "/etc/localtime:/etc/localtime:ro"
#            - "/etc/timezone:/etc/timezone:ro"
#            - hls_vol:/usr/local/srs/objs/nginx/html/live
#        networks:
#            - ffplayout-network
#        ports:
#            - "1935:1935"
#            - "1985:1985"
#        restart: unless-stopped
    ffplayout-engine:
        container_name: ffplayout-engine
#        image: ghetzel/ffplayout-engine:3.0
        build: ./ffplayout-engine
        volumes:
            - "./config/engine:/etc/ffplayout"
            - "./assets/fonts:/usr/share/fonts"
            - "./logs:/var/log/ffplayout"
            - "./tv-media:/tv-media"
            - "./playlists:/playlists"
            - "/etc/localtime:/etc/localtime:ro"
            - "/etc/timezone:/etc/timezone:ro"
            - "./hls_vol:/var/www/live"
        networks:
            - ffplayout-network
        ports:
            - "5555:5555"
            - "9001:9001"
        restart: unless-stopped
#        depends_on:
#            - srs
    ffplayout-api:
        container_name: ffplayout-api
#        image: ghetzel/ffplayout-api:3.0
        build: ./ffplayout-api
        volumes:
            - "./config/engine:/etc/ffplayout"
            - "./logs:/var/log/ffplayout"
            - "./tv-media:/tv-media"
            - "./playlists:/playlists"
            - "/etc/localtime:/etc/localtime:ro"
            - "/etc/timezone:/etc/timezone:ro"
            - "./assets/static:/opt/ffplayout-api/ffplayout/static"
            - "./assets/dbs:/opt/ffplayout-api/ffplayout/dbs"
        networks:
            - ffplayout-network
        ports:
            - "127.0.0.1:8001:8001"
        restart: unless-stopped
        depends_on:
            - ffplayout-engine
    ffplayout-frontend:
        container_name: ffplayout-frontend
#        image: ghetzel/ffplayout-frontend:3.0
        build: ./ffplayout-frontend
        volumes:
            - "./tv-media:/usr/share/nginx/html/tv-media"
            - "./config/frontend.conf:/etc/nginx/conf.d/default.conf"
            - "./assets/static:/usr/share/nginx/html/static"
            - "/etc/localtime:/etc/localtime:ro"
            - "/etc/timezone:/etc/timezone:ro"
            - "./hls_vol:/usr/share/nginx/html/live:ro"
        networks:
            - ffplayout-network
        ports:
            - "127.0.0.1:3000:80"
        restart: unless-stopped
        depends_on:
            - ffplayout-api
volumes:
    hls_vol:
        driver_opts:
            type: tmpfs
            device: tmpfs
            o: "size=1536M"
networks:
    ffplayout-network:
        name: ffplayout-network
        driver: bridge

engine相关配置

先修改engine部分,即使frontend和api不能成功运行,光靠engine也已经可以实现24/7播放。

首先修改./ffplayout-engine/Dockerfile,把版本号改成需要的版本,其他部分不需要修改。

然后在./config/engine中修改配置文件ffplayout-001.yml,这里可以添加多个配置,同时运行多个ffplayout,添加ffplayout配置后在子文件夹supervisor中添加对应的配置即可。
这个文件是engine对应的配置,拉取到的代码是没有更新过的老文件,这里需要到engine的repo中获取需要的配置文件,然后按需求修改,再覆盖ffplayout-001.yml。

输入、输出等设置都在这个配置文件里,如果设置正确,应该可以直接进行直播。

接下来修改supervisor配置,supervisord.conf中,修改password,这是访问supervior监控页面和socket的密码。如果需要生成相关log,在engine-001.conf和supervisord.conf对应的位置设置即可。

api相关配置

api是最复杂的,但如果没有api,frontend也无法正常运行,所以只能先配置api。

我直接重写了Dockerfile,原来的Dockerfile无论怎么改都没法成功部署,系统我也从alpine换成了debian,基本上照搬api的手动部署教程。timeZone,secret,domainName之类的按自己需要修改即可。其中RUN sed -i "s/SOCKET_PASS = 'hsF0wQkl5zopEy1mBlT3g'/SOCKET_PASS = 'xxxxxxxxxxxxx'/g" ffplayout/settings/common.py当中的xxxxxxxxxxxxx需要改成supervior配置中的密码。

FROM python:3.9-buster

ENV DJANGO_SETTINGS_MODULE=ffplayout.settings.production
ARG version=4.0.0
ARG timeZone=Etc/UTC
ARG domainName=ffplayout.stsecurity.moe
ARG serviceUser=root

# get ffplayout-api and all dependencies
RUN apt-get update
RUN apt-get install -y gcc tzdata libffi-dev mediainfo
WORKDIR /opt
RUN wget "https://github.com/ffplayout/ffplayout-api/archive/v${version}.zip"
RUN unzip "./v${version}.zip"
RUN mv "./ffplayout-api-${version}" './ffplayout-api'
RUN rm "v${version}.zip"
WORKDIR /opt/ffplayout-api
RUN pip install --no-cache-dir -r ./requirements-base.txt
RUN mkdir /etc/ffplayout

WORKDIR /opt/ffplayout-api/ffplayout
ARG secret=xxxxxxxxxxxxxxx
RUN sed -i "s/---a-very-important-secret-key-_-generate-it-new---/$secret/g" ffplayout/settings/production.py
RUN sed -i "s/'localhost'/'localhost', \'$domainName\'/g" ffplayout/settings/production.py
RUN sed -i "s/ffplayout\\.local/$domainName\',\n    \'https\\:\/\/$domainName/g" ffplayout/settings/production.py
RUN sed -i "s|TIME_ZONE = 'UTC'|TIME_ZONE = '$timeZone'|g" ffplayout/settings/common.py
RUN sed -i "s/localhost/$domainName/g" ../docs/db_data.json
RUN sed -i "s|MULTI_CHANNEL = False|MULTI_CHANNEL = True|g" ffplayout/settings/common.py
RUN sed -i "s/USE_SOCKET = False/USE_SOCKET = True/g" ffplayout/settings/common.py
RUN sed -i "s/SOCKET_IP = 'localhost'/SOCKET_IP = 'ffplayout-engine'/g" ffplayout/settings/common.py
RUN sed -i "s/SOCKET_PASS = 'hsF0wQkl5zopEy1mBlT3g'/SOCKET_PASS = 'xxxxxxxxxxxxx'/g" ffplayout/settings/common.py
RUN sed -i "s/stream.m3u8/tv.m3u8/g" ../docs/db_data.json

COPY ./assets/start.sh /start.sh

WORKDIR /opt/ffplayout-api/ffplayout
EXPOSE 8001

ENTRYPOINT ["/start.sh"]

然后修改./ffplayout-api/assets文件夹的start.sh,这是api容器的entrypoint。
首先记得把第一行改成#!/bin/bash,不然用sh运行的话会报错。第一个if块会在没有数据库的时候运行,其中有一步会创建管理员账户,username不能通过frontend修改,只能在这里设置好(或者运行之后再进容器里改,太麻烦了)。最后一行是gunicorn的运行命令,我没什么经验,不过这里可以设置log输出路径,有需要的话可以设置。

#!/bin/bash

if [[ ! -f './dbs/player.sqlite3' ]]; then
    python manage.py makemigrations
    python manage.py migrate
    python manage.py loaddata ../docs/db_data.json
    python manage.py shell -c "from django.contrib.auth.models import User; User.objects.create_superuser('username', 'email', 'password')"

    sed -i "s|TIME_ZONE = 'UTC'|TIME_ZONE = '$(cat /etc/timezone)'|g" /opt/ffplayout-api/ffplayout/ffplayout/settings/common.py
fi

if [[ ! -d './static/rest_framework' ]]; then
    python manage.py collectstatic --noinput
fi

/usr/local/bin/gunicorn --workers 4 --worker-class=gevent --env DJANGO_SETTINGS_MODULE=ffplayout.settings.production --timeout 10800 --log-level=info --log-file=- --access-logfile=- --bind 0.0.0.0:8001 ffplayout.wsgi:application

frontend相关配置

frontend的Dockerfile需要修改版本号,并且按情况修改BASE_URL和API_URL,我把API_URL设置为/apireq/在反代时手动分流了一下,这样我就能看出哪些request是来自frontend到api的,实际使用中API_URL保留默认应该就可以,反代规则用官方提供的location ~ ^/(api|admin|auth|api-auth)即可。

frontend容器运行时会运行一个nginx服务,配置在./config/frontend.conf,可以不用修改。

理论上讲可以简化一下这个容器,不运行Nginx,只通过反代的Nginx来serve,不过我懒得试了,也需要重写Dockerfile,不太方便。

Nginx反代

反代配置主要是分流frontend和api,如果用默认的API_URL设置,把/apireq部分的规则改为location ~ ^/(api|admin|auth|api-auth),去掉rewrite,应该就可以了。api部分的header配置来自api的repo内docs文件夹中的ffplayout-api.conf。

conf文件:

server {
  listen 80;
  listen [::]:80;
  server_name ffplayout.stsecurity.moe;

  access_log /var/log/nginx/ffplayout-access.log;
  error_log /var/log/nginx/ffplayout-error.log;

  root /var/www/html;
  location /.well-known/acme-challenge/ { allow all; }
  location / { return 301 https://$host$request_uri; }
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name ffplayout.stsecurity.moe;

  access_log /var/log/nginx/ffplayout-access.log;
  error_log /var/log/nginx/ffplayout-error.log;

  # Uncomment these lines once you acquire a certificate:
  ssl_certificate     /etc/nginx/ssl/ffplayoutfullchain.cer;
  ssl_certificate_key /etc/nginx/ssl/ffplayoutkey.key;

  root /var/www/html;

  location /apireq {
    rewrite ^/apireq(.*)$ $1 break;
    try_files $uri @apiproxy;
  }

  location / {
    add_header 'Access-Control-Allow-Origin' '*';
    try_files $uri @proxy;
  }

  location @proxy {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_pass http://localhost:3000;

  }

  location @apiproxy {

    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

            add_header 'Access-Control-Allow-Origin' "$http_origin" always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
            add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;

        add_header Last-Modified $date_gmt;
        add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
        if_modified_since off;
        expires off;
        etag off;

        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 36000s;
        proxy_connect_timeout 36000s;
        proxy_send_timeout 36000s;
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
        send_timeout 36000s;
        proxy_no_cache 1;
        proxy_pass http://localhost:8001;
  }


}

大功告成

docker-compose build && docker-compose up -d运行。


Sup