Tutorial part 2 - #4
Conversation
… device plugin and network settings, add K8s manifests for secrets, cms, service and deployments
Prevents accidentally committing terraform.tfstate, .pem keys, and tfvars files that may contain credentials or infrastructure state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
d2ccc57 to
9594d7c
Compare
9594d7c to
8ce9008
Compare
| {:plug, "~> 1.14"}, | ||
| {:ex_aws_s3, "~> 2.5"}, | ||
| {:hackney, ">= 0.0.0"}, | ||
| {:credo, ">= 0.0.0", only: :dev, runtime: false} |
There was a problem hiding this comment.
why this only: :dev part was removed? :D
| bitrate: 4_000_000, | ||
| framerate: {30, 1} | ||
| framerate: {30, 1}, | ||
| bitrate: %VariableBitrate{average_bitrate: 5_000_000, max_bitrate: 6_000_000} |
There was a problem hiding this comment.
Let's make max_bitrate a value derived from average_bitrate.
For lifestreams it's good to keep almost CBR so I would say that we should set max_bitrate = 1.1*max_bitrate at most. We can even consider using a real CBR here.
There was a problem hiding this comment.
How about we added some bucket lifecycle configuration to delete objects after given period of time?
(I am afraid people who read the tutorial might forget to remove the objects on their own and will be charged with storage costs)
There was a problem hiding this comment.
Also - wouldn't it be better if we allowed only Cloudfront to access S3?
Now this bucket has their access policy set in "chmod 777" manner :D
If we want to preserve it like that, let's add a comment that it's not advised to use this code on production.
| @@ -0,0 +1,79 @@ | |||
| variable "aws_region" { | |||
| description = "AWS region (must match the ambient provider config)" | |||
There was a problem hiding this comment.
I think provider already uses this variable, so we can remove this "(must match the ambient provider config)" part.
| @@ -0,0 +1,19 @@ | |||
| terraform { | |||
There was a problem hiding this comment.
consider adding required_version key as well
| rules = [ | ||
| { | ||
| rulePriority = 1 | ||
| description = "Expire untagged images after 7 days" |
There was a problem hiding this comment.
Don't we want to add some cleanup policy for tagged images as well?
WDYT?
| } | ||
|
|
||
| # No inbound SSH rule: SSH access goes over an SSM Session Manager tunnel | ||
| # (see ssh.tf), not a direct port 22 opening. |
There was a problem hiding this comment.
I don't think ssh.tf exists 🤔
| count = var.gpu_enabled ? 1 : 0 | ||
| alarm_name = "ex-broadcaster-gpu-utilization-high" | ||
| namespace = "ExBroadcaster" | ||
| metric_name = "utilization_gpu" |
There was a problem hiding this comment.
Shouldn't we use nvidia_smi_utilization_gpu?
https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-NVIDIA-GPU.html
|
|
||
| launch_template { | ||
| id = aws_launch_template.ex_broadcaster.id | ||
| version = "$Latest" |
There was a problem hiding this comment.
I think it would be better to do:
| version = "$Latest" | |
| version = "aws_launch_template.ex_broadcaster.latest_version" |
since with $Latest, even when the template changes, this blueprint stays the same (while it should also be updated).
| which Membrane element uses under the hood. | ||
| In the next chapters we will focus on deploying the application in the cloud environment | ||
| so you won’t need to run the application locally, so this requirement won’t apply anymore. | ||
| For more information, you can take a look at [membrane_transcoder_plugin](https://hexdocs.pm/membrane_transcoder_plugin) which provides hardware-accelerated transcoding capabilities via the `membrane_vk_video_plugin` dependency. |
There was a problem hiding this comment.
I would leave a direct link to the https://crates.io/crates/vk-video somewhere here
| {:membrane_core, "~> 1.2"}, | ||
| {:membrane_vk_video_plugin, "~> 0.2.1"}, | ||
| {:membrane_transcoder_plugin, "~> 0.4.0"}, | ||
| {:membrane_vk_video_plugin, "> 0.0.0"}, |
There was a problem hiding this comment.
[NIT] Wouldn't it be better to do:
| {:membrane_vk_video_plugin, "> 0.0.0"}, | |
| {:membrane_vk_video_plugin, "~> 0.2.0"}, |
?
Also, I don't think we should it should always be added i.e. if somebody is not on Linux with Vulkan support, than he shouldn't add it and we should mention that
| In this chapter we will create an Elixir application and run it locally. | ||
| Since the application will be performing hardware-accelerated video transcoding with the use of Vulkan Video Extensions, | ||
| you need a Linux machine with a Vulkan-capable GPU (NVIDIA or AMD) with Mesa drivers and Vulkan Video extension support. | ||
| The application can benefit from hardware-accelerated video transcoding via Vulkan Video native acceleration on Linux machines with a Vulkan-capable GPU (NVIDIA or AMD) with Mesa drivers and Vulkan Video extension support. It is not required though, `Membrane.Transcoder` will adapt to your environment. |
There was a problem hiding this comment.
I am not sure about this "Membrane.Transcoder will adapt to your environment" though - if I remember correctly, it's user's responsibility to either add or not add membrane_transcoder_plugin's optional dependency to membrane_vk_video_plugin
| width: 1920, | ||
| height: 1080, | ||
| framerate: {30, 1}, | ||
| bitrate: %VariableBitrate{average_bitrate: 5_000_000, max_bitrate: 6_000_000} |
There was a problem hiding this comment.
Don't we want to use ConstantBitrate since it's meant to be used for a livestream?
| @@ -339,7 +385,7 @@ graph LR | |||
|
|
|||
| subgraph "Video branch" | |||
| H264in["Membrane.H264.Parser\n(annexb · au-aligned)"] | |||
There was a problem hiding this comment.
I don't think we need this parser, Membrane.Transcoder will inject it automatically if it's necessary
| **Key improvement in v0.4.0**: Instead of creating three separate `Membrane.Transcoder` instances (one per resolution), | ||
| we now use a single transcoder with three output pads, each configured with different output stream formats. | ||
| This is more efficient as it allows the transcoder to share resources between the different output variants. |
There was a problem hiding this comment.
Let's remove this one as well ;)
|
|
||
| We only need to `terminate: normal` when the end-of-stream signal arrives at `:hls_sink` sink element. | ||
|
|
||
| ### Using membrane_vk_video_plugin directly |
There was a problem hiding this comment.
Do we really want to have this section here?
This first chapter is already quite long so I don't think we need to make it even longer
| }) | ||
| ``` | ||
|
|
||
| #### When to use membrane_vk_video_plugin directly |
| Alternatively, paste it into the [hls.js demo player](https://hlsjs.video-dev.org/demo/) (local HTTP server is configure to allow all CORS origins). | ||
| You should be able to see that the resolution changes throughout based on your network condition or even manually change it. | ||
|
|
||
| ### Streaming from OBS Studio instead of FFmpeg |
There was a problem hiding this comment.
This section also looks a little bit out of the scope for this tutorial 🤔
| - The bottom-right status bar in OBS shows the live connection state; "Reconnecting..." there usually means the | ||
| server isn't reachable at the address/port you configured, not a codec problem. | ||
|
|
||
| ### Watching the stream in VLC |
| Approval is often instant but can take up to a day or two for larger increases — request it first, then move on to | ||
| the rest of the setup while you wait. | ||
|
|
||
| You don't have to sit around waiting for it, though: set `gpu_enabled = false` (in `terraform.tfvars` or via |
There was a problem hiding this comment.
Isn't it variables.tf instead of terraform.tfvars?
| `terraform apply -var="gpu_enabled=false"`) and the ASG falls back to a `c6i.large` (no GPU, no Vulkan Video quota needed) instead | ||
| of `g6.xlarge`, so you can bring up the rest of the stack and exercise it end-to-end immediately. Vulkan Video | ||
| hardware acceleration just won't be available on that fleet, so `Membrane.Transcoder` falls back to software | ||
| encoding — fine for getting everything wired up, but switch back to `gpu_enabled = true` (the default) once your |
There was a problem hiding this comment.
This part of sentence sounds too LLM-like to me, let's rephrase it :D
|
|
||
| ### The S3 bucket | ||
|
|
||
| `s3.tf` provisions the same bucket chapter 1's `S3Storage` writes to, now as part of the infrastructure instead of |
There was a problem hiding this comment.
Will the Elixir application be "automatically" aware of this bucket name?
| } | ||
| ``` | ||
|
|
||
| Two things worth flagging, both left as known limitations rather than hidden: |
There was a problem hiding this comment.
| Two things worth flagging, both left as known limitations rather than hidden: | |
| Two things worth flagging: |
| A couple of details worth calling out: the container's stdout/stderr go straight to CloudWatch Logs via Docker's | ||
| own `awslogs` log driver, no sidecar needed. And port 8080 — the development HTTP server from chapter 1 — is only | ||
| bound to `127.0.0.1`, not exposed to the NLB or the internet; in production, HLS is served from S3 (directly, or | ||
| through a CDN in front of it), so the in-process HTTP server has no reason to be reachable from outside the instance. |
There was a problem hiding this comment.
Let's enumerate it and make it sound more human friendly ;)
| OBS's connection indicator (bottom-right) turning green, with a steady bitrate and no dropped-frames warning, | ||
| means the NLB accepted the connection and RTMP ingest is flowing into the ASG. | ||
|
|
||
| ### Watching the stream with VLC |
There was a problem hiding this comment.
Do we really need to use VLC? In most cases viewers view HLS stream directly in browser, I would say that hls.js is good enough for the sake of testing the output
ce68163 to
593bd02
Compare
593bd02 to
6775378
Compare
| {:ex_aws_s3, "~> 2.5"}, | ||
| {:hackney, ">= 0.0.0"}, | ||
| {:credo, ">= 0.0.0", only: :dev, runtime: false} | ||
| {:credo, ">= 0.0.0", only: :dev, runtime: false, override: true} |
There was a problem hiding this comment.
Why would we need this override: true? 🤔
| Each output pad also carries its own target bitrate (`Membrane.Transcoder.Video.VariableBitrate`), | ||
| so the transcoder encodes a proper bitrate ladder alongside the resolution ladder instead of | ||
| leaving every variant at the encoder's default rate control. |
There was a problem hiding this comment.
I think it's quite obvious that it happens this way, so let's remove this comment
| docker buildx build \ | ||
| --platform linux/amd64 \ | ||
| -t <account-id>.dkr.ecr.<your-aws-region>.amazonaws.com/ex-broadcaster:latest \ | ||
| --push . |
There was a problem hiding this comment.
Don't we need to --build-context transcoder_plugin since the Dockerfile has COPY --from=transcoder_plugin?
| GPU_DOCKER_ARGS=() | ||
|
|
||
| apt-get update -y | ||
| apt-get install -y linux-headers-aws ubuntu-drivers-common gcc-12 nvidia-driver-595 nvidia-utils-595 |
There was a problem hiding this comment.
Do we need to install nvidia-driver no matter if ${gpu_enabled}" = "true"?
| ARG ELIXIR_VERSION=1.17.3 | ||
| ARG OTP_VERSION=27.1.2 |
There was a problem hiding this comment.
Are these two used at all? 🤔
| | 1080p | 1920 × 1080 | 5.0 Mbps / 6.0 Mbps | | ||
| | 720p | 1280 × 720 | 2.8 Mbps / 3.5 Mbps | | ||
| | 480p | 854 × 480 | 1.4 Mbps / 1.75 Mbps | |
There was a problem hiding this comment.
I don't think these bitrates are corresponding to the ones in code (we have @max_bitrate_factor equal to 1.1 and 5*1.1 is 5.5, not 6 :D)
|
|
||
| alias ExBroadcaster.Storages.S3Storage | ||
| alias Membrane.HTTPAdaptiveStream.Storages.FileStorage | ||
| alias Membrane.RTMPServer, as: RTMPServer |
There was a problem hiding this comment.
I don't think this as: RTMPServer is needed :D
Second part of the tutorial, describes deployment, monitoring, and CDN setup.
Do not merge yet, requires changes from membraneframework/membrane_transcoder_plugin#25