parent
d7b91c29dc
commit
604186fc51
@ -0,0 +1,396 @@
|
||||
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 the below to tweak to your needs:
|
||||
|
||||
1. The namespace should be set to whatever you desire, in the below example it runs in the `media` namespace.
|
||||
2. The schedule, in the below 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:v1.15.1
|
||||
imagePullPolicy: IfNotFound
|
||||
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](Trakt-Attributes.md#oauth-flow-using-docker) 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:v1.15.1
|
||||
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
|
||||
```
|
Loading…
Reference in new issue