on
Creating a CUPS print server in Kubernetes
Kubernetes CUPS print server
Why did Reece want to do this?
Printing via CUPS is a very well trodden path. So much so that at Reece we are migrating away from VM’s running CUPS to Kubernetes pods. Adding, deleting and managing printers can be automated via git
to allow a CUPS pod to support the thousands of print jobs we do at Reece every day.
Removing the overhead of managing VM’s and moving to a more structured pipeline as code build and deploy model allows more people to offer ideas and support for printing at Reece.
How are printers shared
The default behaviour of CUPS browsing is to broadcast
printers using IPP on the CUPS instance layer 2 subnet. Whilst this has worked very well for Reece when using VM’s, this does not work when CUPS is moved to K8S as a Docker implementation.
Printers are shared across Layer 3 using the BrowsePoll
directive in the CUPS configuration file required (RHEL 6 and RHEL 7 have different files for this directive).
CUPS uses a discovery protocol allowing CUPS clients
to share printers from the CUPS server
showing as local printers.
Using the new Kubernetes CUPS implementation as a print server is done from a CUPS client polling
for remote printers. Remote CUPS printers are shared over the Internet Printing Protocol (IPP), clients can poll
for printers by updating the required configuration file as shown below
RHEL 6
Update the /etc/cups/cupsd.conf
file to add required directive BrowsePoll <Remote CUPS server>
setting host and port combination, port 631 is implied.
BrowsePoll 192.168.7.20
BrowsePoll 192.168.7.65:631
BrowsePoll host.example.com:631
RHEL 7
Update the /etc/cups/cups-browsed.conf
file to add required directive BrowsePoll <Remote CUPS server>
setting host and port combination, port 631 is implied.
BrowsePoll 192.168.7.20
BrowsePoll 192.168.7.65:631
BrowsePoll host.example.com:631
Adding printers
Adding printers to this CUPS server
is done using a git
repo. Updating the git
repo will trigger a pull from the CUPS pod to add the printer to the CUPS server
instance using the below command
#> /usr/sbin/lpadmin -p <printer> -E -v socket://<printer>:9100 -P /usr/share/cups/model/dummy.ppd
The same git
repo is used as a source of printers required on in the pod, if a printer exists in the pod but not the git
file, the printer is removed with the below command
/usr/sbin/lpadmin -x <printer>
Print queues are kept clean by deleting print jobs queued on the CUPS server using the simple Linux one-liner below, looking for processes older than 60 seconds owned by the lp
user and deleting the process.
local SEC=60
ps -eo pid=,euser=,etimes= | while read pid user sec; do if [[ "${sec}" -gt "${SEC}" ]] && [[ "${user}" == "lp" ]]; then echo "${pid}" | xargs kill; fi; done
Persistence
Whist adding persistence sounds like a really good thing for a print server, this has not been decided as a useful function for Reece. The main issue with persistence is that we do not really care about in flight print jobs, the users will just re-print. If the add print queues script can add all
the print queues at startup quickly, we do not need persistence using a PVC.
Persistence is easy to create and maintain in Kubernetes, but at the moment this is not required for our CUPS implementation.
Helm
Deployment at Reece is handled by Helm. The objects to deploy are shown below
- Kubernetes Deployment object
- NodePort exposed Service object
- ConfigMap profiling deployment i.e. DEV or PROD
Side car pods
This CUPS implementation in Kubernetes is a bit tricky. We need to add and delete printers when a git
file is changed and we need to send print jobs through a third party template engine to parse XML into PCL. These complexities are handled by the use of init containers
and side car
pods. This is out of scope for this post.
Locking down printer sharing
Using the /etc/cups/cupsd.conf
configuration file, we can lock down CIDR addresses that are allowed to print through these print servers. Using the below stanza in the configuration file, we can limit clients
to the 10.99
supernet.
<Location />
Order allow,deny
Allow 10.99
</Location>
What print jobs go to the third party template engine
This is handled using the interface
/ filter
file (/usr/lib/cups/filter/custom). This file has logic to parse custom printing jobs from plain text.
Custom printing filter for DocOrigin
Using a third party template engine for print jobs makes CUPS a little harder to wrangle. A custom filter
and ppd
is required to send print jobs to the third party template engine.
To enable XML printing through a third party, the below files are required:
- Custom convs file to match text/plain
- Dummy PPD file to run print jobs through filters
- Custom filter
Custom files and print job flow
Adding a print.convs
file to the /etc/cups/
directory allows for MIME types to be handled by custom filters. The below simple custom convs
file takes all text/plain
print jobs being converted to application/octet-stream
and sends it to the custom
filter. The 10
is a cost, the lowest cost gets used out of all convs
files.
text/plain application/octet-stream 10 custom
A custom dummy
PPD file is used to point printers to application/octet-stream
as an output and is placed into the /usr/share/cups/model/
directory. This PPD is used when creating printers.
*PPD-Adobe: "4.3"
*%
*% Dummy PPD
*%
*FormatVersion: "4.3"
*FileVersion: "1.1"
*LanguageVersion: English
*LanguageEncoding: ISOLatin1
*PCFileName: "dummy.ppd"
*Manufacturer: "Dummy"
*Product: "(SNMPOFF)"
*cupsFilter: "application/octet-stream 0 -"
*cupsSNMPSupplies: False
*ModelName: "Dummy"
*ShortNickName: "Dummy"
*NickName: "Dummy"
Finally we need the custom
filter which is placed into the /usr/lib/cups/filter/
directory with the filename matching the convs
lines last column (custom
).
These files combine to pass all print jobs destined to a printer added with the dummy PPD to be output as application/octet-stream
, as all of our print jobs present as the MIME type text/plain
we use the convs
file to sent the print jobs to the custom
filter.