From 004c62bea4095b2b612da29706b96a5ed6d5512a Mon Sep 17 00:00:00 2001 From: monktan Date: Mon, 13 Sep 2021 21:16:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0k8s=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build_docker_images.sh | 72 +++++++++++++++++++++++++-- docker/build_docker_images.sh | 7 +++ dockerfile | 82 +++++++++++++++++++++++++++++++ k8s_readme.md | 31 ++++++++++++ server/main.cpp | 2 +- tests/CMakeLists.txt | 4 +- tests/default.pem | 89 ++++++++++++++++++++++++++++++++++ tests/ssl.p12 | Bin 2441 -> 0 bytes 8 files changed, 279 insertions(+), 8 deletions(-) create mode 100644 docker/build_docker_images.sh create mode 100644 dockerfile create mode 100644 k8s_readme.md create mode 100644 tests/default.pem delete mode 100644 tests/ssl.p12 diff --git a/build_docker_images.sh b/build_docker_images.sh index 0ee40722..95dde88a 100644 --- a/build_docker_images.sh +++ b/build_docker_images.sh @@ -1,7 +1,69 @@ #!/bin/bash set -e -docker build -t gemfield/zlmediakit:20.04-runtime-ubuntu18.04 -f docker/ubuntu18.04/Dockerfile.runtime . -docker build -t gemfield/zlmediakit:20.04-devel-ubuntu18.04 -f docker/ubuntu18.04/Dockerfile.devel . -docker build -t gemfield/zlmediakit:20.04-runtime-ubuntu16.04 -f docker/ubuntu16.04/Dockerfile.runtime . -docker build -t gemfield/zlmediakit:20.04-devel-ubuntu16.04 -f docker/ubuntu16.04/Dockerfile.devel . -docker build -t gemfield/zlmediakit:centos7-runtime -f docker/centos7/Dockerfile.runtime . +while getopts c:t:m:v: opt +do + case $opt in + t) + type=$OPTARG + ;; + v) + version=$OPTARG + ;; + m) + model=$OPTARG + ;; + ?) + echo "unkonwn" + exit + ;; + esac +done + +if [[ ! -n $type ]];then + echo ".sh [-t build|push] [-m Debug|Release] [-v [version]]" + exit +fi + +if [[ ! -n $model ]];then + echo ".sh [-t build|push] [-m Debug|Release] [-v [version]]" + exit +fi + +if [[ ! -n $version ]];then + echo "use latest no version set" + version="latest" +fi + +case $model in + 'Debug') + ;; + 'Release') + ;; + *) + echo "unkonwn model" + echo ".sh [-t build|push] [-m Debug|Release] [-v [version]]" + exit + ;; +esac + +namespace="zlmediakit" +packagename="zlm-mediaserver" + +case $type in + 'build') + rm -rf ./build/CMakeCache.txt + # 以腾讯云账号为例 + docker build --build-arg MODEL=$model -t ccr.ccs.tencentyun.com/$namespace/$packagename:$model.$version . + ;; + 'push') + echo "push to dst registry" + # 以腾讯云账号为例 + docker login --username=default_name ccr.ccs.tencentyun.com + docker push ccr.ccs.tencentyun.com/$namespace/$packagename:$model.$version + ;; + *) + echo "unkonwn type" + echo ".sh [-t build|push] [-m Debug|Release] [-v [version]]" + exit + ;; +esac diff --git a/docker/build_docker_images.sh b/docker/build_docker_images.sh new file mode 100644 index 00000000..65deff30 --- /dev/null +++ b/docker/build_docker_images.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +docker build -t gemfield/zlmediakit:20.04-runtime-ubuntu18.04 -f ubuntu18.04/Dockerfile.runtime . +docker build -t gemfield/zlmediakit:20.04-devel-ubuntu18.04 -f ubuntu18.04/Dockerfile.devel . +docker build -t gemfield/zlmediakit:20.04-runtime-ubuntu16.04 -f ubuntu16.04/Dockerfile.runtime . +docker build -t gemfield/zlmediakit:20.04-devel-ubuntu16.04 -f ubuntu16.04/Dockerfile.devel . +docker build -t gemfield/zlmediakit:centos7-runtime -f centos7/Dockerfile.runtime . diff --git a/dockerfile b/dockerfile new file mode 100644 index 00000000..b287285b --- /dev/null +++ b/dockerfile @@ -0,0 +1,82 @@ +FROM ubuntu:18.04 AS build +ARG MODEL +#shell,rtmp,rtsp,rtsps,http,https,rtp +EXPOSE 9000/tcp +EXPOSE 1935/tcp +EXPOSE 554/tcp +EXPOSE 322/tcp +EXPOSE 80/tcp +EXPOSE 443/tcp +EXPOSE 10000/udp +EXPOSE 10000/tcp +EXPOSE 8000/udp + +RUN apt-get update && \ + DEBIAN_FRONTEND="noninteractive" \ + apt-get install -y --no-install-recommends \ + build-essential \ + cmake \ + git \ + curl \ + vim \ + wget \ + ca-certificates \ + tzdata \ + libssl-dev \ + libmysqlclient-dev \ + libx264-dev \ + libfaac-dev \ + gcc \ + g++ \ + gdb \ + libmp4v2-dev && \ + apt-get autoremove -y && \ + apt-get clean -y && \ + wget https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz -O libsrtp-2.2.0.tar.gz && tar xfv libsrtp-2.2.0.tar.gz && \ + cd libsrtp-2.2.0 && ./configure --enable-openssl && make && make install && \ + rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /opt/media +COPY . /opt/media/ZLMediaKit +WORKDIR /opt/media/ZLMediaKit +#RUN git submodule update --init --recursive && \ +RUN mkdir -p build release/linux/${MODEL}/ + +WORKDIR /opt/media/ZLMediaKit/build +RUN cmake -DCMAKE_BUILD_TYPE=${MODEL} -DENABLE_WEBRTC=true -DENABLE_TESTS=false -DENABLE_API=false .. && \ + make -j8 + +FROM ubuntu:18.04 +ARG MODEL + +RUN apt-get update && \ + DEBIAN_FRONTEND="noninteractive" \ + apt-get install -y --no-install-recommends \ + vim \ + wget \ + ca-certificates \ + tzdata \ + curl \ + libssl-dev \ + libx264-dev \ + libfaac-dev \ + ffmpeg \ + gcc \ + g++ \ + gdb \ + libmp4v2-dev && \ + apt-get autoremove -y && \ + apt-get clean -y && \ + rm -rf /var/lib/apt/lists/* + +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ + && echo $TZ > /etc/timezone && \ + mkdir -p /opt/media/bin/www + +WORKDIR /opt/media/bin/ +COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/MediaServer /opt/media/ZLMediaKit/tests/default.pem /opt/media/bin/ +COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/config.ini /opt/media/conf/ +COPY --from=build /opt/media/ZLMediaKit/www/ /opt/media/bin/www/ +ENV PATH /opt/media/bin:$PATH +CMD ["sh","-c","./MediaServer -s default.pem -c ../conf/config.ini"] diff --git a/k8s_readme.md b/k8s_readme.md new file mode 100644 index 00000000..2de7cfda --- /dev/null +++ b/k8s_readme.md @@ -0,0 +1,31 @@ +# k8s部署建议 + +## 编译 + +- 方式一 + + 可以自己写脚本编译 + +- 方式二 + + 可以使用自带`build_docker_images.sh`脚本编译,具体参见[部署](##部署) + +## 部署 +- 可以是用根目录下面build_docker_images.sh脚本进行编译与推送到指定仓库 + + - 推送 + + 推送之前务必修改脚本中`镜像仓库用户名与仓库地址`。有需要也可以同时修改`命名空间与包名`。 + + - 编译 + + ```shell + sh build_docker_images.sh [-t build|push] [-m Debug|Release] [-v [version]] + -t: 指定编译类型,build 编译镜像 push 推送到指定仓库 + -m: 编译类型 + -v:版本号 + ``` + +- 如果需要自定义配置文件,可以使用`configMap`挂载到pod中`/opt/media/conf/`目录来覆盖默认配置文件 +- 如果需要自定义证书,请替换源码目录`tests`目录下面的`default.pem`证书文件,zlmedia在pod启动时候会默认加载。 + diff --git a/server/main.cpp b/server/main.cpp index e3c4d672..291d1b8f 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -141,7 +141,7 @@ public: (*_parser) << Option('s',/*该选项简称,如果是\x00则说明无简称*/ "ssl",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ Option::ArgRequired,/*该选项后面必须跟值*/ - (exeDir() + "ssl.p12").data(),/*该选项默认值*/ + (exeDir() + "default.pem").data(),/*该选项默认值*/ false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ "ssl证书文件或文件夹,支持p12/pem类型",/*该选项说明文字*/ nullptr); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 23f3cdf2..1452d9fb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,10 +1,10 @@ -execute_process(COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/ssl.p12 ${EXECUTABLE_OUTPUT_PATH}/) +execute_process(COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/default.pem ${EXECUTABLE_OUTPUT_PATH}/) aux_source_directory(. TEST_SRC_LIST) foreach (TEST_SRC ${TEST_SRC_LIST}) if (NOT ENABLE_WEBRTC) # 暂时过滤掉依赖 WebRTC 的测试模块 - if ("${TEST_SRC}" MATCHES "test_rtcp_nack\.cpp") + if ("${TEST_SRC}" MATCHES "test_rtcp_nack.cpp") continue() endif () endif () diff --git a/tests/default.pem b/tests/default.pem new file mode 100644 index 00000000..2cef8d27 --- /dev/null +++ b/tests/default.pem @@ -0,0 +1,89 @@ +-----BEGIN CERTIFICATE----- +MIIGATCCBOmgAwIBAgIQC+3yGsMpfdf8+/3qyyeKMjANBgkqhkiG9w0BAQsFADBu +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg +RFYgVExTIENBIC0gRzEwHhcNMjEwOTEzMDAwMDAwWhcNMjIwOTEzMjM1OTU5WjAh +MR8wHQYDVQQDExZkZWZhdWx0LnpsbWVkaWFraXQuY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAiTtbBLkfqhvnBehH+4hgdaR+b+Y36YLc6+B9Mrcm +3Z400xbLB79xOkpVQ3XDsikJS3oCTwigBwOco9ulTnkB2zKnWo0WeqBgyoI1g+uk +S/Cjmocl4Pq2bhWWTCHmPqhSkikEHXixfUPHzAUhRDSXnhxFnJX2HW+LyR5rnspw +7PkWuhepurIoLq/tVGKnO5OX/2cOmInAPGjrBG+SFa1Z0+D0lCTextP7CKirMQyo +IrUR0ZDwFTREROjwxYTXrUZw3Wrv2JQI+HW/sf71Mp0b0qwFDA8xBvtn9PBrG83d +20GMGoXMqwa8W4gsevczZxq24fC+W8UEX2YIc+MCOwqxJQIDAQABo4IC5jCCAuIw +HwYDVR0jBBgwFoAUVXRPsnJP9WC6UNHX5lFcmgGHGtcwHQYDVR0OBBYEFHnDhdRf +5j6H38/6FDh5dUxSafFeMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j +b20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjA+BgNVHSAENzA1MDMGBmeBDAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3 +LmRpZ2ljZXJ0LmNvbS9DUFMwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj +ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx +LmNydDAJBgNVHRMEAjAAMIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdwApeb7w +njk5IfBWc59jpXflvld9nGAK+PlNXSZcJV3HhAAAAXvfJJeIAAAEAwBIMEYCIQDn +tznKOZ7m++ulwy19YpiX7nSYbtLK3X2oEOglBNPWeQIhAKtxstW4CPWeqS0skXy3 +Bfl3INTuswwya4V93LRxU11qAHUAUaOw9f0BeZxWbbg3eI8MpHrMGyfL956IQpoN +/tSLBeUAAAF73ySX8wAABAMARjBEAiBJXGMdexhrjMMRBtYl8HATnpr01wV7glwt +RfnU4ymvBQIgP0jIov7IKd3MM54OOzQ6lzrN7kYtw+Dxnk12dMldlJkAdgBByMqx +3yJGShDGoToJQodeTjGLGwPr60vHaPCQYpYG9gAAAXvfJJfGAAAEAwBHMEUCIDbB +3Pc+Sr3rZ84ou3AfbTK/1JZ0p2T2MulEIfgftqqqAiEAzsSmKwwiq4QhL4v8Vgci +0Z8cN5lNdaq5ofNpBGWnaD8wDQYJKoZIhvcNAQELBQADggEBAA8NRtfJgzD/gXku +n83SQp1g2ZPDIJYALegUu90fpc9OzJjdFOeaqw0s3futhTaJrwRU8CRITQnMeaj+ +6rHNWxtQp7A3E2/U2R5E374x4T8Z0fJD+WqANNYVnbt0Kw7bOrY8lRizyL5KMbSj +DKjatMhXAvWMS1Bj0LXBQFtnoBQKo83VQdBxKUK5cK3EN//jrT1IUf/g4R0taQ3f +G18oSCc1kE/giaDnZhEjv8R2KLr4fOC9j7paigZHKYEolO4CMq30hADYDZ06IGjU +vNgB0dkmcr2S7xlQ5creU/8tUmUodidcN4/4PCpUV9X7WVGHO5+F8B205/gEydVc +dduKrjk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH +MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc +oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo +lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj +pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h +yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n +wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M +pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf +BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C +AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp +Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu +Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG +/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT +MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B +SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW +M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV +4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ +sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy +rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg== +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAiTtbBLkfqhvnBehH+4hgdaR+b+Y36YLc6+B9Mrcm3Z400xbL +B79xOkpVQ3XDsikJS3oCTwigBwOco9ulTnkB2zKnWo0WeqBgyoI1g+ukS/Cjmocl +4Pq2bhWWTCHmPqhSkikEHXixfUPHzAUhRDSXnhxFnJX2HW+LyR5rnspw7PkWuhep +urIoLq/tVGKnO5OX/2cOmInAPGjrBG+SFa1Z0+D0lCTextP7CKirMQyoIrUR0ZDw +FTREROjwxYTXrUZw3Wrv2JQI+HW/sf71Mp0b0qwFDA8xBvtn9PBrG83d20GMGoXM +qwa8W4gsevczZxq24fC+W8UEX2YIc+MCOwqxJQIDAQABAoIBABdmIf5brFz+dfVJ +ZmCqn7vfaNmemQD9Wbr0Y5SOqxdVnu8xLzwqdd15CDHA9jW+DoIqkxMzxsl7Ya2E +yZpoQptD90oWzXLqPqa47fQI7VIvfU3fZmOGjC2YC7D+hLpBTBb03GlEB4tyz7Hn +XAU7rHB+pJXu8fCR8PVBdRs1rGyTSlOV/o2Q9dnnoAX6AIDWi63ChEJCrl0Mo9pL +e1naRN3hPFJ3SzJnPB6AUhGlxxV0oc71fnPpvW11tlpWshbVXIHJ2yS1zQcTvshj +pbOC9KRsbRQPJA+NEGOEciCP9H/Lh0lFle/zzSbpzetwgPlqTuaPbFwknz6xGux4 +GaBLrH8CgYEAvP6lGIX3a2Ot04bYoQPfO3vRUPigFKCPKQgj62B/M2SVayuMmeAF +9WvbWHLP/gx+vg7d3Pmg5YGeFnWSnrOAfjW4758yhA5f3/XDkSsMqc/NT+bsURuS +Z+sDzeOwYp8kznDPaj0k4XhUSej+6vYM+zsbMzqqsGkTQj1Ncv5eohcCgYEAueKp +MPZtYte8ID/g//p9YPCg9FNiuGMMAvUyig1PQrChSwiLflSoDJsbrtD69IliP+4y +hWeD/X8w+DUv34aQQIsSwoci6gEmG8ErqVOYvCjxksgM+2r0cGJVxqaGUB0gdjah +Z/7ND/yByr0qxuz8izaGfDybGo5F0QB13692uCMCgYAaQ/mF0vhzwEKkJxVsKzGW +/ro0WplExJugxDTZvWtwJQZvAnpj2DJ7zSWKwUoOsIXcvAwxba/itYTW8jgSPjgZ +UjYFd0Z5+9VvNqSbRDRaVTrfY+Rr0T0jnBHHR2F4E032Ms9goGbDvwlXzD3BQbjE +IY7CK+EU60V16zccSCW2uQKBgQC2GJYYEgA8aQyxJwK6oN98PJ3gW2OFL/pPV3aI +CNvRgAix4ZANVM8/ch9fVPfS4FbwO98gErUZeyU0sZ3RQhhEMjlReWK5jCCR5d1o +xi0EfrOQUAtvrGoDQkG3FeDT0ITBaWka4GBwPbPEMSYbs4L+uY5rXE+xZxh70xCl +7VTGswKBgQCOrkDjkzgwg1+GvEFmhUi+N/UaYXzOUvx6ciuxPwb+dyMy1jGpsYhE +1eLJuwtpda2aZpvLzM8HC8a7Amkff/kw8n4d0y0iM2xQ9nZSMx1pZXTCToMK2EDJ +b54tt/amcS1qFF7H3CBDYrkHcr+qPL2ypr+onOGkU3MufFXZk7NcVA== +-----END RSA PRIVATE KEY----- diff --git a/tests/ssl.p12 b/tests/ssl.p12 deleted file mode 100644 index fe42e11f15c7ec50cfb3548f797df00c39cefa65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2441 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