How to run a cron job inside a docker container?

筅森魡賤 提交于 2019-11-25 23:35:52

问题


I am trying to run a cronjob inside a docker container that invokes a shell script.

Yesterday I have been searching all over the web and stack overflow, but I could not really find a solution that works.
How can I do this?

EDIT:

I\'ve created a (commented) github repository with a working docker cron container that invokes a shell script at given interval.


回答1:


You can copy your crontab into an image, in order for the container launched from said image to run the job.

See "Run a cron job with Docker" from Julien Boulay in his Ekito/docker-cron:

Let’s create a new file called "hello-cron" to describe our job.

* * * * * echo "Hello world" >> /var/log/cron.log 2>&1
# An empty line is required at the end of this file for a valid cron file.

The following Dockerfile describes all the steps to build your image

FROM ubuntu:latest
MAINTAINER docker@ekito.fr

RUN apt-get update && apt-get -y install cron

# Copy hello-cron file to the cron.d directory
COPY hello-cron /etc/cron.d/hello-cron

# Give execution rights on the cron job
RUN chmod 0644 /etc/cron.d/hello-cron

# Apply cron job
RUN crontab /etc/cron.d/hello-cron

# Create the log file to be able to run tail
RUN touch /var/log/cron.log

# Run the command on container startup
CMD cron && tail -f /var/log/cron.log

(see Gaafar's comment and How do I make apt-get install less noisy?:
apt-get -y install -qq --force-yes cron can work too)


OR, make sure your job itself redirect directly to stdout/stderr instead of a log file, as described in hugoShaka's answer:

 * * * * * root echo hello > /proc/1/fd/1 2>/proc/1/fd/2

Replace the last Dockerfile line with

CMD ["cron", "-f"]

See also (about cron -f, which is to say cron "foreground") "docker ubuntu cron -f is not working"


Build and run it:

sudo docker build --rm -t ekito/cron-example .
sudo docker run -t -i ekito/cron-example

Be patient, wait for 2 minutes and your commandline should display:

Hello world
Hello world

Eric adds in the comments:

Do note that tail may not display the correct file if it is created during image build.
If that is the case, you need to create or touch the file during container runtime in order for tail to pick up the correct file.

See "Output of tail -f at the end of a docker CMD is not showing".




回答2:


The adopted solution may be dangerous in a production environment.

In docker you should only execute one process per container because if you don't, the process that forked and went background is not monitored and may stop without you knowing it.

When you use CMD cron && tail -f /var/log/cron.log the cron process basically fork in order to execute cron in background, the main process exits and let you execute tailf in foreground. The background cron process could stop or fail you won't notice, your container will still run silently and your orchestration tool will not restart it.

You can avoid such a thing by redirecting directly the cron's commands output into your docker stdout and stderr which are located respectively in /proc/1/fd/1 and /proc/1/fd/2.

Using basic shell redirects you may want to do something like this :

* * * * * root echo hello > /proc/1/fd/1 2>/proc/1/fd/2

And your CMD will be : CMD ["cron", "-f"]




回答3:


For those who wants to use a simple and lightweight image:

FROM alpine:3.6

# copy crontabs for root user
COPY config/cronjobs /etc/crontabs/root

# start crond with log level 8 in foreground, output to stderr
CMD ["crond", "-f", "-d", "8"]

Where cronjobs is the file that contains your cronjobs, in this form:

* * * * * echo "hello stackoverflow" >> /test_file 2>&1
# remember to end this file with an empty new line



回答4:


What @VonC has suggested is nice but I prefer doing all cron job configuration in one line. This would avoid cross platform issues like cronjob location and you don't need a separate cron file.

FROM ubuntu:latest

# Install cron
RUN apt-get -y install cron

# Create the log file to be able to run tail
RUN touch /var/log/cron.log

# Setup cron job
RUN (crontab -l ; echo "* * * * * echo "Hello world" >> /var/log/cron.log") | crontab

# Run the command on container startup
CMD cron && tail -f /var/log/cron.log

After running your docker container, you can make sure if cron service is working by:

# To check if the job is scheduled
docker exec -ti <your-container-id> bash -c "crontab -l"
# To check if the cron service is running
docker exec -ti <your-container-id> bash -c "pgrep cron"

If you prefer to have ENTRYPOINT instead of CMD, then you can substitute the CMD above with

ENTRYPOINT cron start && tail -f /var/log/cron.log



回答5:


There is another way to do it, is to use Tasker, a task runner that has cron (a scheduler) support.

Why ? Sometimes to run a cron job, you have to mix, your base image (python, java, nodejs, ruby) with the crond. That means another image to maintain. Tasker avoid that by decoupling the crond and you container. You can just focus on the image that you want to execute your commands, and configure Tasker to use it.

Here an docker-compose.yml file, that will run some tasks for you

version: "2"

services:
    tasker:
        image: strm/tasker
        volumes:
            - "/var/run/docker.sock:/var/run/docker.sock"
        environment:
            configuration: |
                logging:
                    level:
                        ROOT: WARN
                        org.springframework.web: WARN
                        sh.strm: DEBUG
                schedule:
                    - every: minute
                      task: hello
                    - every: minute
                      task: helloFromPython
                    - every: minute
                      task: helloFromNode
                tasks:
                    docker:
                        - name: hello
                          image: debian:jessie
                          script:
                              - echo Hello world from Tasker
                        - name: helloFromPython
                          image: python:3-slim
                          script:
                              - python -c 'print("Hello world from python")'
                        - name: helloFromNode
                          image: node:8
                          script:
                              - node -e 'console.log("Hello from node")'

There are 3 tasks there, all of them will run every minute (every: minute), and each of them will execute the script code, inside the image defined in image section.

Just run docker-compose up, and see it working. Here is the Tasker repo with the full documentation:

http://github.com/opsxcq/tasker




回答6:


VonC's answer is pretty thorough. In addition I'd like to add one thing that helped me. If you just want to run a cron job without tailing a file, you'd be tempted to just remove the && tail -f /var/log/cron.log from the cron command.

However this will cause the Docker container to exit shortly after running because when the cron command completes, Docker thinks the last command has exited and hence kills the container. This can be avoided by running cron in the foreground via cron -f.




回答7:


I created a Docker image based on the other answers, which can be used like

docker run -v "/path/to/cron:/etc/cron.d/crontab" gaafar/cron

where /path/to/cron: absolute path to crontab file, or you can use it as a base in a Dockerfile:

FROM gaafar/cron

# COPY crontab file in the cron directory
COPY crontab /etc/cron.d/crontab

# Add your commands here

For reference, the image is here.




回答8:


Though this aims to run jobs beside a running process in a container via Docker's exec interface, this may be of interest for you.

I've written a daemon that observes containers and schedules jobs, defined in their metadata, on them. Example:

version: '2'

services:
  wordpress:
    image: wordpress
  mysql:
    image: mariadb
    volumes:
      - ./database_dumps:/dumps
    labels:
      deck-chores.dump.command: sh -c "mysqldump --all-databases > /dumps/dump-$$(date -Idate)"
      deck-chores.dump.interval: daily

'Classic', cron-like configuration is also possible.

Here are the docs, here's the image repository.




回答9:


When you deploy your container on another host, just note that it won't start any processes automatically. You need to make sure that 'cron' service is running inside your container. In our case, I am using Supervisord with other services to start cron service.

[program:misc]
command=/etc/init.d/cron restart
user=root
autostart=true
autorestart=true
stderr_logfile=/var/log/misc-cron.err.log
stdout_logfile=/var/log/misc-cron.out.log
priority=998



回答10:


Define the cronjob in a dedicated container which runs the command via docker exec to your service.

This is higher cohesion and the running script will have access to the environment variables you have defined for your service.

#docker-compose.yml
version: "3.3"
services:
    myservice:
      environment:
        MSG: i'm being cronjobbed, every minute!
      image: alpine
      container_name: myservice
      command: tail -f /dev/null

    cronjobber:
     image: docker:edge
     volumes:
      - /var/run/docker.sock:/var/run/docker.sock
     container_name: cronjobber
     command: >
          sh -c "
          echo '* * * * * docker exec myservice printenv | grep MSG' > /etc/crontabs/root
          && crond -f"



回答11:


Cron jobs are stored in /var/spool/cron/crontabs (Common place in all distros I Know). BTW, You can create a cron tab in bash using something like that:

crontab -l > cronexample
echo "00 09 * * 1-5 echo hello" >> cronexample
crontab cronexample
rm cronexample

This will create a temporary file with cron task, then program it using crontab. Last line remove temporary file.




回答12:


When running on some trimmed down images that restrict root access, I had to add my user to the sudoers and run as sudo cron

FROM node:8.6.0
RUN apt-get update && apt-get install -y cron sudo

COPY crontab /etc/cron.d/my-cron
RUN chmod 0644 /etc/cron.d/my-cron
RUN touch /var/log/cron.log

# Allow node user to start cron daemon with sudo
RUN echo 'node ALL=NOPASSWD: /usr/sbin/cron' >>/etc/sudoers

ENTRYPOINT sudo cron && tail -f /var/log/cron.log

Maybe that helps someone




回答13:


So, my problem was the same. The fix was to change the command section in the docker-compose.yml.

From

command: crontab /etc/crontab && tail -f /etc/crontab

To

command: crontab /etc/crontab

command: tail -f /etc/crontab

The problem was the '&&' between the commands. After deleting this, it was all fine.




回答14:


The most robust way I found so far is run an independent cron container - install the docker client and bind mount the docker sock so you can talk to the docker server on the host.

Then just use env vars for each cron job and an entrypoint script to generate the /etc/crontab

Here is an image that I created using this principle and using it in production for the last 3-4 years.

https://www.vip-consult.solutions/post/better-docker-cron#content




回答15:


Try using clockwork gem to schedule tasks. Follow the steps provided in this link.

http://fuzzyblog.io/blog/rails/2017/05/11/adding-cron-to-a-dockerized-rails-application-using-clockwork.html

You can call the rake task inside lib/clock.rb file as below.

every(1.day, 'Import large data from csv files', :at => '5:00') do |job|
  `rake 'portal:import_data_from_csv'`
end

Create a separate container in docker-compose file & run the below command inside the container.

command: bundle exec clockwork lib/clock.rb


来源:https://stackoverflow.com/questions/37458287/how-to-run-a-cron-job-inside-a-docker-container

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!