这是关于设计和构建非平凡的容器化解决方案的多部分系列的第二部分。我们正在使用现成的组件和一些自制软件制作广播电台,所有这些都在 Docker、Docker Compose 以及最终的 Kubernetes 之上。

在这一部分中,我们将采用我们在上一部分中制定的架构,看看我们是否不能让它在容器中运行,只使用 Docker 本身。

今天,我们要建一个广播电台。我们在我们的最后一篇文章中制定了架构:

[Alt](https://res.cloudinary.com/practicaldev/image/fetch/s--u5UBA6_2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev- to-uploads.s3.amazonaws.com/uploads/articles/oebipl4fmwdjyk02nrpz.png)

卷起袖子,我们将开始建造一些容器。

采购音频 - youtube-dl

在我们可以广播音频之前,我们必须有音频,我们将从 YouTube 获取我们的音频。那么,这就是我们的第一个容器。

我们将从 Ubuntu 20.04 (Focal Fossa) 基础镜像开始。我喜欢这张照片,不仅因为它熟悉又温馨;还可以访问apt进行软件包管理,以及(更重要的是)所有那些甜美的 Debian 软件包。例如,我们需要 Python(youtube-dl 是用 Python 编写的):

FROM ubuntu:20.04
RUN apt-get update \
 && apt-get install -y python3 python3-pip \
 && pip3 install youtube-dl

进入全屏模式 退出全屏模式

我们将在我们的镜像中设置一个ENTRYPOINT,这样当我们docker run时,我们可以向它传递参数,并像使用 youtube-dl 本身一样使用容器。

FROM ubuntu:20.04
RUN apt-get update \
 && apt-get install -y python3 python3-pip \
 && pip3 install youtube-dl

WORKDIR /radio
ENTRYPOINT ["youtube-dl"]

进入全屏模式 退出全屏模式

让我们运行到目前为止的内容:

$ docker build -t youtube-dl .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM ubuntu:20.04
 ---> f63181f19b2f
Step 2/2 : RUN apt-get update  && apt-get install -y python3 python3-pip  && pip3 install youtube-dl
 ---> Running in cbc3f65f6028
<a whole bunch of apt-get output...>
Collecting youtube-dl
  Downloading youtube_dl-2021.3.14-py2.py3-none-any.whl (1.9 MB)
Installing collected packages: youtube-dl
Successfully installed youtube-dl-2021.3.14
Removing intermediate container cbc3f65f6028
 ---> 66648c847e34
Successfully built 66648c847e34
Successfully tagged youtube-dl:latest

$ docker run youtube-dl --version
2021.03.14

进入全屏模式 退出全屏模式

由于我们使用了ENTRYPOINT,我们给容器的--version参数通过 youtube-dl 传递。这很重要,因为这将是我们与 youtube-dl 交互的主要方式。

让我们尝试下载我的播客 Rent / Buy / Build 的一些回剧集。你可以找到那些here

$ docker run youtube-dl https://youtu.be/D7hxDYBoHvQ?list=PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8
[youtube:tab] Downloading playlist PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8 - add --no-playlist to just download video D7hxDYBoHvQ
[youtube:tab] PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8: Downloading webpage
[youtube:tab] PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8: Downloading webpage
[download] Downloading playlist: Rent / Buy / Build Episodes
[youtube:tab] playlist Rent / Buy / Build Episodes: Downloading 1 videos
[download] Downloading video 1 of 1
[youtube] D7hxDYBoHvQ: Downloading webpage
[youtube] D7hxDYBoHvQ: Downloading MPD manifest
[download] Destination: Episode 0 - What's All This Then-D7hxDYBoHvQ.mp4
[download] 100% of 12.76MiB in 00:0296MiB/s ETA 00:003
[download] Finished downloading playlist: Rent / Buy / Build Episodes

进入全屏模式 退出全屏模式

看起来我们得到了文件,但它去哪儿了?进入乙醚。当我们的容器进程 (youtube-dl) 退出(即完成下载和转码视频)时,容器的根文件系统将被擦除。这是一个特点,老实说。如果我们想将我们创建的任何文件保留在容器中,我们将需要绑定挂载到一些持久(-ish)存储中。如果您还记得,我们将WORKDIR设置为/radio,因此如果我们可以从容器的 outside 挂载一个目录,我们可以将这些文件保存更长时间。

$ mkdir radio
$ docker run -v $PWD/radio:/radio youtube-dl https://youtu.be/D7hxDYBoHvQ?list=PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8
[youtube:tab] Downloading playlist PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8 - add --no-playlist to just download video D7hxDYBoHvQ
[youtube:tab] PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8: Downloading webpage
[youtube:tab] PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8: Downloading webpage
[download] Downloading playlist: Rent / Buy / Build Episodes
[youtube:tab] playlist Rent / Buy / Build Episodes: Downloading 1 videos
[download] Downloading video 1 of 1
[youtube] D7hxDYBoHvQ: Downloading webpage
[youtube] D7hxDYBoHvQ: Downloading MPD manifest
[download] Destination: Episode 0 - What's All This Then-D7hxDYBoHvQ.mp4
[download] 100% of 12.76MiB in 00:0218MiB/s ETA 00:004
[download] Finished downloading playlist: Rent / Buy / Build Episodes

$ ls -l radio
total 13312
-rw-r--r-- 1 jhunt staff 13383936 Mar 22 14:30 "Episode 0 - What's All This Then-D7hxDYBoHvQ.mp4"

进入全屏模式 退出全屏模式

耶!文件!太糟糕了,它们是视频文件,而不是音频文件。为了解决这个问题,让我们将 ffmpeg 放入我们的容器映像中。

FROM ubuntu:20.04
RUN apt-get update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y python3 python3-pip ffmpeg \
 && pip3 install youtube-dl

WORKDIR /radio
ENTRYPOINT ["youtube-dl"]

进入全屏模式 退出全屏模式

(注意:我必须添加那个DEBIAN_FRONTEND=noninteractive位,以防止包管理器因终端提示时区数据而打扰我。)

使用 ffmpeg,我们可以获取来自 youtube-dl 的文件,删除视频组件,并将音频转换为通用格式(这在我们进行 LiquidSoap 编程时会很有用)。

$ docker run -v $PWD/radio:/radio --entrypoint ffmpeg youtube-dl \
  -i "Episode 0 - What's All This Then-D7hxDYBoHvQ.mp4" \
  -vn -c:a libopus \
  "Episode 0 - What's All This Then-D7hxDYBoHvQ.opus"

进入全屏模式 退出全屏模式

这是关于文件格式和转换的相当多的领域专业知识,而且它就在那里,公开的。这也是一个两步的过程。这是一个可以通过一个小的 shell 脚本来解决的问题。

#!/bin/sh
set -eu

mkdir -p /tmp/ytdl.$$
cd /tmp/ytdl.$$
for url in "$@"; do
  youtube-dl -x "$url"
  for f in *; do
    ffmpeg -i "$f" -vn -c:a libopus "$f.opus"
    rm "$f"
    mv "$f.opus" /radio
  done
done
cd /
rm -rf /tmp/ytdl.$$
ls -1 /radio/*.opus > /radio/playlist.m3u

进入全屏模式 退出全屏模式

这个 shell 脚本中有一个新功能:播放列表管理。在我们将新文件摄取到/radio后,我们会重建一个.m3u播放列表。这是一个简单的文件:每行一个文件路径,按顺序读取。将ls输出通过sort管道确保我们有稳定的播放列表顺序;如果他们愿意,我们会让其他系统随机播放列表排序。

我们可以通过 Dockerfile 将该脚本(我将其命名为entrypoint)复制到我们的 docker 镜像中:

FROM ubuntu:20.04
RUN apt-get update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y python3 python3-pip ffmpeg \
 && pip install youtube-dl

WORKDIR /radio

COPY entrypoint /usr/bin/entrypoint
RUN chmod 0755 /usr/bin/entrypoint
ENTRYPOINT ["entrypoint"]

进入全屏模式 退出全屏模式

有了这个改变,我们回到调用我们的容器(在一个新的构建之后!)只使用我们想要下载和转码的 URL:

$ docker run -v $PWD/radio:/radio youtube-dl https://youtu.be/D7hxDYBoHvQ?list=PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8
[youtube:tab] Downloading playlist PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8 - add --no-playlist to just download video D7hxDYBoHvQ
[youtube:tab] PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8: Downloading webpage
[youtube:tab] PLGDWYlG32yKoYAuREw9eSLFIdWJZ_3rW8: Downloading webpage
[download] Downloading playlist: Rent / Buy / Build Episodes
[youtube:tab] playlist Rent / Buy / Build Episodes: Downloading 1 videos
[download] Downloading video 1 of 1
[youtube] D7hxDYBoHvQ: Downloading webpage
[youtube] D7hxDYBoHvQ: Downloading MPD manifest
[download] Destination: Episode 0 - What's All This Then-D7hxDYBoHvQ.m4a
[download] 100% of 9.73MiB in 00:0123MiB/s ETA 00:005
[ffmpeg] Correcting container in "Episode 0 - What's All This Then-D7hxDYBoHvQ.m4a"
[ffmpeg] Post-process file Episode 0 - What's All This Then-D7hxDYBoHvQ.m4a exists, skipping
[download] Finished downloading playlist: Rent / Buy / Build Episodes
ffmpeg version 4.2.4-1ubuntu0.1 Copyright (c) 2000-2020 the FFmpeg developers
  built with gcc 9 (Ubuntu 9.3.0-10ubuntu2)
  configuration: --prefix=/usr --extra-version=1ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'Episode 0 - What's All This Then-D7hxDYBoHvQ.m4a':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2mp41
    encoder         : Lavf58.29.100
  Duration: 00:10:30.10, start: 0.000000, bitrate: 129 kb/s
    Stream #0:0(eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc.
Stream mapping:
  Stream #0:0 -> #0:0 (aac (native) -> opus (libopus))
Press [q] to stop, [?] for help
[libopus @ 0x560757a9e280] No bit rate set. Defaulting to 96000 bps.
Output #0, opus, to 'Episode 0 - What's All This Then-D7hxDYBoHvQ.m4a.opus':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2mp41
    encoder         : Lavf58.29.100
    Stream #0:0(eng): Audio: opus (libopus), 48000 Hz, stereo, flt, 96 kb/s (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc.
      encoder         : Lavc58.54.100 libopus
      major_brand     : isom
      minor_version   : 512
      compatible_brands: isomiso2mp41
size=    6139kB time=00:10:30.11 bitrate=  79.8kbits/s speed=72.5x
video:0kB audio:6085kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.876994%

$ ls -l radio
total 6144
-rw-r--r-- 1 jhunt staff 6286163 Mar 22 14:52 "Episode 0 - What's All This Then-D7hxDYBoHvQ.m4a.opus"
-rw-r--r-- 1 jhunt staff      61 Mar 22 14:52  playlist.m3u

进入全屏模式 退出全屏模式

我们甚至可以做多个!

$ docker run -v $PWD/radio:/radio youtube-dl \
  ... \
  ... \
  ...

进入全屏模式 退出全屏模式

您正在使用 Icecast 进行直播

是时候打开发射器并开始广播了!为此,我们需要 Icecast。和以前一样,我们将从 Ubuntu 20.04 基础开始并添加我们需要的包。

FROM ubuntu:20.04
RUN apt-get update \
 && apt-get install -y icecast2

进入全屏模式 退出全屏模式

Icecast 不会让任何人将音乐流式传输到它。那将是纯粹的混乱!这是一个 broadcast 广播电台,而不是一个对等免费的广播电台。我们选择了曲子。我们挑选广告。我们写歌谣。为了避免 riff-raff,Icecast 使用了 Speakeasy 身份验证。在我们接受其音频流之前,每个源驱动程序都需要提供秘密密码。

Icecast 核心团队肯定有幽默感,因为在默认配置中,源密码是“hackme”。我们需要改变这一点。

重要的是要记住,Icecast 诞生于 XML 风靡一时,并在通过环境变量配置事物成为时尚之前的时代到来。修改源驱动密码,我们需要修改配置文件:/etc/icecast2/icecast.xml

FROM ubuntu:20.04
ARG PASSWORD
RUN apt-get update \
 && apt-get install -y icecast2 \
 && sed -i -e "s/>hackme</>$PASSWORD</" /etc/icecast2/icecast.xml

进入全屏模式 退出全屏模式

这个 sed 命令查找">hackme<"的所有实例并将其替换为">$PASSWORD<"。新的ARG指令设置了一个 Docker build 参数,它有点像一个环境变量,仅在容器映像为 built 时才存在。我们这样指定它:

$ docker build --build-arg PASSWORD=sekrit -t icecast .

进入全屏模式 退出全屏模式

让我们试一试,确保一切都是船形的。

$ docker run icecast icecast2 -c /etc/icecast2/icecast.xml
[2021-03-22  19:04:15] WARN CONFIG/_parse_root Warning,  not configured, using default value "localhost". This will cause problems, e.g. with YP directory listings.
[2021-03-22  19:04:15] WARN CONFIG/_parse_root Warning,  not configured, using default value "Earth".
[2021-03-22  19:04:15] WARN CONFIG/_parse_root Warning,  contact not configured, using default value "icemaster@localhost".
[2021-03-22  19:04:15] WARN fserve/fserve_recheck_mime_types Cannot open mime types file /etc/mime.types
ERROR: You should not run icecast2 as root
Use the changeowner directive in the config file

进入全屏模式 退出全屏模式

德拉特。

Icecast 不想以 root 身份运行,因此我们需要在容器内配置一个用户帐户。

FROM ubuntu:20.04
ARG PASSWORD
RUN apt-get update \
 && apt-get install -y icecast2 \
 && sed -i -e "s/>hackme</>$PASSWORD</" /etc/icecast2/icecast.xml \
 && useradd radio \
 && chown -R radio:radio /etc/icecast2 /var/log/icecast2
USER radio
ENTRYPOINT ["icecast2"]
CMD ["-c", "/etc/icecast2/icecast.xml"]

进入全屏模式 退出全屏模式

useradd命令更新容器内的文件(特别是/etc/passwd),而USER指令告诉 Docker 在容器启动时它应该切换到那个有效的 UID。

我还冒昧地设置了一个入口点和一些参数。当您同时指定它们时,您可以为 docker 映像 (ENTRYPOINT) 的基本命令提供“默认”参数 (CMD)。

这就是 Icecast 图像的全部内容。运行它需要更多的努力,但并不多。值得注意的是,我们需要提供一个端口,以便听众可以找到广播电台,并且源驱动程序可以输入他们的音频。我们通过将 Docker 主机的端口转发到容器中来做到这一点,如下所示:

$ docker run -p 8000:8000 icecast
[2021-03-22  19:11:48] WARN CONFIG/_parse_root Warning,  not configured, using default value "localhost". This will cause problems, e.g. with YP directory listings.
[2021-03-22  19:11:48] WARN CONFIG/_parse_root Warning,  not configured, using default value "Earth".
[2021-03-22  19:11:48] WARN CONFIG/_parse_root Warning,  contact not configured, using default value "icemaster@localhost".
[2021-03-22  19:11:48] WARN fserve/fserve_recheck_mime_types Cannot open mime types file /etc/mime.types

进入全屏模式 退出全屏模式

在容器命名空间内,Icecast 将所有接口绑定到端口 80。在外部,Docker(在iptables的帮助下)正在将 8000 端口上的 host 的流量转发到容器的 8000 端口。(顺便说一下,这通常_已完成通过 Linux 网桥和 veth 对的两端,但这是一个不同的、更书呆子的帖子的主题。)

您可以在 docker ps 输出中看到端口分配:

$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED        STATUS                  PORTS                  NAMES
52993e74a1b3   icecast   "icecast2 -c /etc/ic…"   1 second ago   Up Less than a second   0.0.0.0:8000->8000/tcp   friendly_albattani

进入全屏模式 退出全屏模式

也可以通过 netstat 或 lsof (选择你最喜欢的!):

# lsof -nP -i TCP
com.docke 29389 jhunt   41u     IPv6  0xb3ce8809472e12f         0t0                 TCP *:8000 (LISTEN)

进入全屏模式 退出全屏模式

让我们把 Icecast 的想法留给它,然后继续设置我们的音频源。

LiquidSoap 的魔力

什么是LiquidSoap?魔法。

[Alt](https://res.cloudinary.com/practicaldev/image/fetch/s--PxiTpCBF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev- to-uploads.s3.amazonaws.com/uploads/articles/oam6fwbrats63fwb31m5.png)

我是真的,而且是认真的意思。他们的网站比较谦虚:

LiquidSoap 是一种用于描述音频和视频流的强大而灵活的语言。它提供了丰富的运算符集合,您可以随意组合这些运算符,为您提供比创建或转换流所需的更多功能。

比你需要的更多的力量——这大概总结了我们将要使用它的目的。我们有从一些互联网视频中提取的音频文件,我们有一个无线电广播引擎,正等待提供一些多汁的音频流。我们需要一种方法来连接它们。我们将使用 LiquidSoap。

这是我们将要使用的简陋的 .liq 脚本:

output.icecast(%opus,
  host = "10.128.0.56",
  port = 8000,
  password = "sekrit",
  mount = "pirate-radio.opus",
  playlist.safe(reload=120,"/radio/playlist.m3u"))

进入全屏模式 退出全屏模式

但在我们超越自己之前,让我们构建我们的 Docker 镜像来运行那个小宝石。

你知道该怎么做;从 Ubuntu 开始并添加您需要的软件包。正如我们迄今为止对每个图像所做的那样,我们将设置一个ENTRYPOINT以便我们可以假装我们的容器_is_liquidsoap命令。

FROM ubuntu:20.04
RUN apt-get update \
 && apt-get install -y liquidsoap
USER nobody
ENTRYPOINT ["liquidsoap"]

进入全屏模式 退出全屏模式

与 Icecast 一样,我们设置了USER,这样 Liquidsoap 就不会因为我们是 root 用户而生气。但是,这一次,我们将使用 Ubuntu 附带的默认nobody帐户。

确信图像合适且良好,我们可以重新访问该.liq脚本。这是一个带有更多评论的版本。

# radio.liq
#
# This script streams audio from files on-disk,
# in an m3u playlist, to our Icecast server in
# opus format.
#
output.icecast(%opus,
  host = "10.128.0.56",        # where is Icecast?
  port = 8000,                 # what port is it bound on?
  password = "sekrit",         # what's the secret pass phrase?
  mount = "pirate-radio.opus", # what should we call this stream?

  # create an infallible playlist (hence 'safe')
  # using the files on-disk, in /radio.
  #
  playlist.safe(reload=120,"/radio/playlist.m3u"))

进入全屏模式 退出全屏模式

(请记住,LiquidSoap 是一种成熟的编程语言。你可以用它做各种疯狂的事情。)

值得注意的是,我们将向我们的 Icecast 服务器输出一个流。因为容器有它自己的网络堆栈(容器!),所以我已经将 Docker 主机的 IP itself 指定为 Icecast 主机端点。这会导致数据包路由出容器,并最终通过我们的端口转发规则进入另一个容器。在本系列的下一部分中,当我们将 Docker Compose 引入图片时,我们将充分打磨那个特别粗糙的补丁。

还记得我们在通过 youtube-dl 摄取新音频文件时创建的 m3u 播放列表吗?这就是 LiquidSoap 脚本在构建其音频流时将要使用的内容。我想指出一开始可能并不明显的事情:当我们的脚本开始使用它们时,播放列表中指定的路径必须是有效的。将所有内容始终安装在/radio就足够了,但这不是唯一的方法。

这就是它,拼图的最后一块。让我们启动那个 docker 容器,看看它是否有效。

$ docker run -d --rm --name icecast -p 8000:8000 icecast
$ docker run --name liquid -v $PWD/radio:/radio 'output.icecast(%opus, host=...'
...
2021/03/22 20:09:17 [playlist(dot)m3u:3] Loading playlist...
2021/03/22 20:09:17 [playlist(dot)m3u:3] No mime type specified, trying autodetection.
2021/03/22 20:09:17 [playlist(dot)m3u:3] Playlist treated as format application/x-mpegURL
2021/03/22 20:09:17 [decoder:3] Method "OGG" accepted "/radio/Episode 0 - What's All This Then-D7hxDYBoHvQ.m4a.opus".
2021/03/22 20:09:17 [playlist(dot)m3u:3] Successfully loaded a playlist of 1 tracks.
2021/03/22 20:09:17 [decoder:3] Method "OGG" accepted "/radio/Episode 0 - What's All This Then-D7hxDYBoHvQ.m4a.opus".
2021/03/22 20:09:17 [playlist(dot)m3u:3] Prepared "/radio/Episode 0 - What's All This Then-D7hxDYBoHvQ.m4a.opus" (RID 2).
2021/03/22 20:09:17 [pirate-radio(dot)opus:3] Connecting mount pirate-radio.opus for source@192.168.88.225...
2021/03/22 20:09:17 [pirate-radio(dot)opus:3] Connection setup was successful.
2021/03/22 20:09:17 [clock.wallclock_main:3] Streaming loop starts, synchronized with wallclock.

进入全屏模式 退出全屏模式

LiquidSoap 容器的日志清楚地表明源能够连接并开始音频流。现在我们可以访问 Web 界面 (http://:8000),它应该看起来像这样:

[Alt](https://res.cloudinary.com/practicaldev/image/fetch/s--_-6zOW8B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https:// dev-to-uploads.s3.amazonaws.com/uploads/articles/lq5eew67hyyr7cdoqe2y.png)

恭喜。你是正式的网络电台 DJ。现在,单击播放按钮,欣赏节目,然后集思广益,制作更好的收音机手柄。

在本系列的下一部分中,我们将收集所有端口转发规则、命令和卷绑定挂载,并使用 Docker Compose 将它们包装在一个简洁的小包中。当我们迭代整个系统时,这将使我们的生活更轻松。

Logo

云原生社区为您提供最前沿的新闻资讯和知识内容

更多推荐