# Kubernetes Walkthrough This article will walk you through getting Plex-Meta-Manager [PMM] set up and running in Kubernetes. It will cover: 1. Creating the Kubernetes CronJob 1. Creating configuration files as Config Maps 1. (Advanced) Creating dynamic configuration files with an Init Container ## Prerequisites. This walk through assumes you are familiar with Kubernetes concepts and have an exiting cluster to deploy into. If you do not, but are interested, [minikube](https://minikube.sigs.k8s.io/docs/start/) is a great place to start. ## Creating the Kubernetes CronJob When running PMM in Kubernetes, executing it as a CronJob gives us the ability to define a schedule for execution and have Kubernetes manage the rest. Some parts of this to tweak to your needs: 1. The namespace should be set to whatever you desire, in this example it runs in the `media` namespace. 2. The schedule, in this example it runs at 00:00 UTC. [https://crontab.guru/](https://crontab.guru/) is a good site if you aren't sure on how to create a schedule. ``` apiVersion: batch/v1 kind: CronJob metadata: name: plex-media-manager namespace: media spec: schedule: "0 0 * * *" jobTemplate: spec: template: spec: securityContext: runAsUser: 1000 runAsGroup: 1000 containers: - name: plex-media-manager image: meisnate12/plex-meta-manager:latest imagePullPolicy: IfNotPresent args: [ "--run", "--read-only-config" ] resources: limits: cpu: 100m memory: 256Mi requests: cpu: 100m memory: 125Mi volumeMounts: - name: config mountPath: /config - name: pmm-config mountPath: /config/config.yml subPath: config.yml - name: movie-config mountPath: /config/movies.yaml subPath: movies.yaml - name: tv-config mountPath: /config/tv.yaml subPath: tv.yaml volumes: - name: config persistentVolumeClaim: claimName: plex-media-manager - configMap: name: pmm-config name: pmm-config - configMap: name: movie-config name: movie-config - configMap: name: tv-config name: tv-config restartPolicy: OnFailure ``` This CronJob also requires 1. A Persistent Volume Claim 2. 3 Config Maps (see next section) The Persistent Volume Claim (PVC) can be as simple as: ``` apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: app: plex-media-manager name: plex-media-manager namespace: media spec: accessModes: - ReadWriteOnce resources: requests: storage: 128Mi ``` ## Creating the Config Maps In Kubernetes, configurations are managed via Config Maps. So we deploy the configurations for PMM as config maps. The minimum requirement is the PMM config, but the example here assumes you have a separate config for movies and tv shows. ### PMM Config Here's a config map for the `config.yml` file for PMM. Note there are many placeholders that will need update based on your environment and needs. Follow the [Trakt Attributes](../../config/trakt) directions for generating the OAuth authorization values. ``` apiVersion: v1 data: config.yml: | libraries: Movies: metadata_path: - file: config/movies.yaml TV Shows: metadata_path: - file: config/tv.yaml settings: cache: true cache_expiration: 60 asset_directory: config/assets asset_folders: true asset_depth: 0 create_asset_folders: false dimensional_asset_rename: false download_url_assets: false show_missing_season_assets: false sync_mode: append minimum_items: 1 default_collection_order: delete_below_minimum: true delete_not_scheduled: false run_again_delay: 2 missing_only_released: false only_filter_missing: false show_unmanaged: true show_filtered: false show_options: false show_missing: true show_missing_assets: true save_missing: true tvdb_language: eng ignore_ids: ignore_imdb_ids: playlist_sync_to_user: all verify_ssl: true plex: url: http://PLEX_IP_HERE:32400 token: YOUR_TOKEN_HERE timeout: 60 clean_bundles: false empty_trash: false optimize: false tmdb: apikey: YOUR_API_KEY_HERE language: en tautulli: url: http://TAUTULLI_IP_HERE:8182 apikey: TAUTULLI_API_KEY_HERE omdb: apikey: OMDB_API_KEY radarr: url: http://RADARR_IP_HERE:7878 token: RADARR_TOKEN_HERE add_missing: false root_folder_path: /movies monitor: false availability: cinemas quality_profile: HD - 720p/1080p tag: pmm add_existing: false search: false radarr_path: plex_path: sonarr: url: http://SONARR_IP_HERE:8989 token: SONARR_TOKEN_HERE add_missing: false add_existing: false root_folder_path: /tv monitor: pilot quality_profile: HD - 720p/1080p language_profile: English series_type: standard season_folder: true tag: pmm search: true cutoff_search: false sonarr_path: plex_path: trakt: client_id: YOUR_CLIENT_ID_HERE client_secret: YOUR_CLIENT_SECRET_HERE authorization: access_token: YOUR_ACCESS_TOKEN_HERE token_type: Bearer expires_in: 7889237 refresh_token: YOUR_REFERSH_TOKEN_HERE scope: public created_at: 1642462048 kind: ConfigMap metadata: name: pmm-config namespace: media ``` ### Movie Config Map Config maps for collections (movies in this example) are more simple! ``` apiVersion: v1 data: movies.yaml: | collections: Trakt Popular: trakt_popular: 200 collection_order: custom sync_mode: sync sort_title: Traktpopular summary: The most popular movies for all time. radarr_add_missing: true radarr_search: true radarr_monitor: true Tautulli Most Popular Movies: sync_mode: sync collection_order: custom tautulli_watched: list_days: 180 list_size: 10 list_minimum: 1 kind: ConfigMap metadata: name: movie-config namespace: media ``` ### TV Config Map ``` apiVersion: v1 data: tv.yaml: | collections: Most Popular: smart_label: originally_available.desc sync_mode: sync imdb_list: url: https://www.imdb.com/search/title/?title_type=tv_series,tv_miniseries limit: 10 summary: The 10 most popular shows across the internet sonarr_add_missing: true sonarr_search: true sonarr_monitor: pilot Tautulli Most Popular: sync_mode: sync collection_order: custom summary: The 10 most popular shows from Plex users tautulli_popular: list_days: 180 list_size: 10 kind: ConfigMap metadata: name: tv-config namespace: media ``` ## Creating dynamic configuration files with an Init Container IMDb search results may include results for media which has not yet been released, resulting in a collection that is incomplete. In order to solve for this you can replace a static config map with a config file that is (re)generated when the cronjob starts each time. This can be done by including an init container which renders a [Jinja](https://jinja.palletsprojects.com/en/3.0.x/templates/) template to a file in the PVC. ### Including the Init Container in the Cron Job NOTE the environment value nameed `JINJA_DEST_FILE` is the resulting name of the generated config file. ``` apiVersion: batch/v1 kind: CronJob metadata: name: plex-media-manager namespace: media spec: schedule: "0 0 * * *" jobTemplate: spec: template: spec: securityContext: runAsUser: 1000 runAsGroup: 1000 initContainers: - name: render-dynamic-config image: chrisjohnson00/jinja-init:v1.0.0 env: # source and destination files - name: JINJA_SRC_FILE value: /config_src/tv.yaml - name: JINJA_DEST_FILE value: /config/tv.yaml # let's be verbose - name: VERBOSE value: "1" volumeMounts: # configMap mount point - name: tv-config-template mountPath: /config_src # target directory mount point; the final config file will be created here - name: config mountPath: /config containers: - name: plex-media-manager image: meisnate12/plex-meta-manager:latest imagePullPolicy: Always args: [ "--run", "--read-only-config" ] resources: limits: cpu: 100m memory: 256Mi requests: cpu: 100m memory: 125Mi volumeMounts: - name: config mountPath: /config - name: pmm-config mountPath: /config/config.yml subPath: config.yml - name: movie-config mountPath: /config/movies.yaml subPath: movies.yaml volumes: - name: config persistentVolumeClaim: claimName: plex-media-manager - configMap: name: pmm-config name: pmm-config - configMap: name: movie-config name: movie-config - configMap: name: tv-config-jinja-template name: tv-config-template restartPolicy: OnFailure ``` ### Templatizing your configuration This example will (re)generate the IMBD list URL and include the current date as the end date for the `release_date` value. `https://www.imdb.com/search/title/?title_type=tv_series,tv_miniseries&release_date=1980-01-01,{{ now().strftime('%Y-%m-%d') }}` `{{ now().strftime('%Y-%m-%d') }}` is the Jinja code, which when rendered will be replaced with the current date in YYYY-MM-DD format. `now()` is a special method defined in the Python code running in the init container to allow access to the current date, so changing the output format is as simple as changing the string in `strftime` to your desired date/time format for your list source. ``` apiVersion: v1 data: tv.yaml: | collections: Most Popular: smart_label: originally_available.desc sync_mode: sync imdb_list: url: https://www.imdb.com/search/title/?title_type=tv_series,tv_miniseries&release_date=1980-01-01,{{ now().strftime('%Y-%m-%d') }} limit: 10 summary: The 10 most popular shows across the internet sonarr_add_missing: true sonarr_search: true sonarr_monitor: pilot Tautulli Most Popular: sync_mode: sync collection_order: custom summary: The 10 most popular shows from Plex users tautulli_popular: list_days: 180 list_size: 10 kind: ConfigMap metadata: name: tv-config-jinja-template namespace: media ```