Führen Sie regelmäßig Python Script mit Docker Compose + Daemon (cron) aus.

Führen Sie regelmäßig Python Script mit Docker Compose + Daemon (cron) aus.

Einführung

`Dies ist ein Artikel über die Mac-Umgebung, aber die Vorgehensweise ist für die Windows-Umgebung dieselbe. Bitte lesen und probieren Sie den umweltabhängigen Teil. ``

Zweck

Starten Sie Daemon (cron) und Database (PostgreSQL) mit Docker Compose und führen Sie Python Script regelmäßig aus.

Nachdem Sie diesen Artikel bis zum Ende gelesen haben, können Sie:

No. Überblick Stichwort
1 Docker Compose docker-compose
2 cron crond
3 SQLAlchemy sqlalchemy
4 PostgreSQL psycopg2
5 Alembic alembic

Ausführungsumgebung

Umgebung Ver.
macOS Catalina 10.15.6
Docker 19.03.12
Python 3.7.3
PostgreSQL 11.5
alembic 1.4.2
psycopg2-binary 2.8.6
requests 2.24.0
SQLAlchemy 1.3.19

Quellcode

Ich denke, dass Sie Ihr Verständnis vertiefen können, indem Sie den Implementierungsinhalten und dem Quellcode tatsächlich folgen. Bitte benutzen Sie es auf jeden Fall.

GitHub

Zum Thema passende Artikel

Systemkonfiguration

Szenario

Speichern Sie die kumulierte Anzahl der täglichen Qiita-Zugriffe.

  1. Starten Sie Daemon (cron) und Database (PostgreSQL) mit Docker Compose
  2. Stellen Sie die Startzeit des Daemons (cron) auf 23:00 ein
  3. Führen Sie das durch den Start von Daemon (cron) ausgelöste Python-Skript aus.
  4. Holen Sie sich Qiita-Informationen mit Python Script und registrieren Sie sie in PostgreSQL

Ausführungsergebnis

Docker

docker.sh


qiita=# SELECT * from qiita ORDER BY date DESC, page_views_count DESC;
 id |    date    |                                   title                                   | page_views_count | likes_count |                         url                          |         created_at
----+------------+---------------------------------------------------------------------------+------------------+-------------+------------------------------------------------------+----------------------------
  1 | 2020-09-10 |Abrufen von Salesforce-Daten mithilfe der REST-API|            11571 |          23 | https://qiita.com/nsuhara/items/19cf8ec89b88fb3deb39 | 2020-09-10 12:48:23.512842
  2 | 2020-09-10 |Alexa Fähigkeit in Python/In Lambda implementiert|             4888 |          11 | https://qiita.com/nsuhara/items/5b19cfb5ffb897bd4cfd | 2020-09-10 12:48:23.513017
  3 | 2020-09-10 |Beschränken Sie Salesforce-Anhänge|             4611 |           6 | https://qiita.com/nsuhara/items/bd41c9ad946b8b832207 | 2020-09-10 12:48:23.51307
  4 | 2020-09-10 |Unit Test für Swift(Unit Test)Wie man ... macht|             4214 |          12 | https://qiita.com/nsuhara/items/bc06c07ff30a5b78696d | 2020-09-10 12:48:23.51313
  5 | 2020-09-10 |IOS mit Kony App Platform/Erstellen Sie eine Android-App|             3612 |           3 | https://qiita.com/nsuhara/items/c28d838492512850520c | 2020-09-10 12:48:23.513182
  6 | 2020-09-10 | Flask-SQLAlchemy +Erstellen Sie einen Webdienst in PostgreSQL|             2842 |           7 | https://qiita.com/nsuhara/items/fa5998c0b2f4fcefbed4 | 2020-09-10 12:48:23.513237
  7 | 2020-09-10 | Heroku + Selenium +Automatisieren Sie den WEB-Prozess mit Chrome|             2719 |          15 | https://qiita.com/nsuhara/items/76ae132734b7e2b352dd | 2020-09-10 12:48:23.513292
  8 | 2020-09-10 | Docker +Erstellen Sie einen Webdienst mit Flask|             2368 |           7 | https://qiita.com/nsuhara/items/c7eff7fae3801f85b5cd | 2020-09-10 12:48:23.513343
  9 | 2020-09-10 |Führen Sie Python regelmäßig auf Heroku Scheduler aus|             2232 |           4 | https://qiita.com/nsuhara/items/fac20adb6b0a122a3709 | 2020-09-10 12:48:23.513393
 10 | 2020-09-10 | Messaging API + LIFF + Heroku + Flask +Erstellen Sie einen LINE BOT mit der Framework-Erweiterung|             1913 |           8 | https://qiita.com/nsuhara/items/0c431913165e4af0f8f5 | 2020-09-10 12:48:23.513449
 11 | 2020-09-10 |Salesforce Lightning-Datendienst(LDS)lernen|             1636 |           4 | https://qiita.com/nsuhara/items/ecd77def7aa1f985efcc | 2020-09-10 12:48:23.513503
 12 | 2020-09-10 |Erstellen Sie mit Flask einen RESTful-Webdienst|             1571 |           5 | https://qiita.com/nsuhara/items/449835bc94f0fb3bbcbd | 2020-09-10 12:48:23.513553
 13 | 2020-09-10 | AWS-Lambda +AWS CSV-Daten mit Python-Schreiben Sie an S3|             1565 |           2 | https://qiita.com/nsuhara/items/b2bd1d2623bca0f767f8 | 2020-09-10 12:48:23.513604
 14 | 2020-09-10 |IOS erstellt mit Kony App Platform/Erfahren Sie mehr über das Codieren von Android-Apps|             1536 |           1 | https://qiita.com/nsuhara/items/bf0e8884a7efc3c55176 | 2020-09-10 12:48:23.513654
 15 | 2020-09-10 |Von JSON zu Word mit Python(docx)Erschaffen|             1511 |           2 | https://qiita.com/nsuhara/items/3ba2fa7db38acd04f448 | 2020-09-10 12:48:23.513708
 16 | 2020-09-10 | AWS-Lambda +AWS mit Python-RDS/Lesen Sie die PostgreSQL-Tabelle|             1408 |           3 | https://qiita.com/nsuhara/items/dd780c2622258d10f961 | 2020-09-10 12:48:23.51376
 17 | 2020-09-10 | AWS-Lambda + Python +Führen Sie mit Cron regelmäßig WEB-Scraping durch|             1342 |           5 | https://qiita.com/nsuhara/items/0d36600511fc162827f6 | 2020-09-10 12:48:23.513819
 18 | 2020-09-10 | Django + SQLAlchemy + SQLite3 /Erstellen Sie eine Web-App in PostgreSQL|             1287 |           3 | https://qiita.com/nsuhara/items/4ab5366273082ee0aa73 | 2020-09-10 12:48:23.513874
 19 | 2020-09-10 |IOS erstellt mit Kony App Platform/Datenverbindung zwischen Android App und Salesforce|             1088 |           1 | https://qiita.com/nsuhara/items/756120f1bddc6f8fe78b | 2020-09-10 12:48:23.513928
 20 | 2020-09-10 |IOS erstellt mit Kony App Platform/Erfahren Sie mehr über das automatische Layout für Android-Apps|              898 |           0 | https://qiita.com/nsuhara/items/a52abd9861c51823ecec | 2020-09-10 12:48:23.513984
 21 | 2020-09-10 |AWS mit Python-S3 signiert(Mit einem Zeitlimit)URL generieren|              723 |           2 | https://qiita.com/nsuhara/items/20160b080c2b30d57729 | 2020-09-10 12:48:23.514131
 22 | 2020-09-10 |Kong API Gateway GUI/Baue Konga|              665 |           3 | https://qiita.com/nsuhara/items/a0de75e6767f98cc8fec | 2020-09-10 12:48:23.51419
 23 | 2020-09-10 |Erstellen Sie ein entferntes Auto mit Raspberry Pi und Python|              637 |           2 | https://qiita.com/nsuhara/items/7970b5dfe95ea76c93d6 | 2020-09-10 12:48:23.514248
 24 | 2020-09-10 ||              535 |           1 | https://qiita.com/nsuhara/items/ad1d8fa1faad7940b5c1 | 2020-09-10 12:48:23.514297
 25 | 2020-09-10 |Vom Einrichten des Raspberry Pi bis zur Installation der Python-Umgebung|              529 |           0 | https://qiita.com/nsuhara/items/05a2b41d94ced1f54316 | 2020-09-10 12:48:23.514365
 26 | 2020-09-10 |LCD mit Raspberry Pi und Python(16x2)Erstelle ein Spiel|              424 |           0 | https://qiita.com/nsuhara/items/57105fd232feffbcd05c | 2020-09-10 12:48:23.514416
 27 | 2020-09-10 |Erstellen Sie eine WEB-Überwachungskamera mit Raspberry Pi und OpenCV|              392 |           0 | https://qiita.com/nsuhara/items/37fcbc9d0e8209080032 | 2020-09-10 12:48:23.514466
 28 | 2020-09-10 | Flask-So implementieren Sie UPSERT mit SQL Alchemy|              389 |           0 | https://qiita.com/nsuhara/items/86570f789093222252b1 | 2020-09-10 12:48:23.514518
 29 | 2020-09-10 | Qiita API +Erhalten Sie Artikelbesuche und Likes in Python|              332 |           0 | https://qiita.com/nsuhara/items/b27b84f0150c3f6534ec | 2020-09-10 12:48:23.514569
 30 | 2020-09-10 |So fügen Sie CSV-Daten stapelweise in PostgreSQL ein|              145 |           0 | https://qiita.com/nsuhara/items/a1b75e0557ed134c5302 | 2020-09-10 12:48:23.514635
(30 rows)

Heroku

2020-09-10.png

App-Konfiguration

tree.sh


/
├── Dockerfiles
│   ├── app
│   │   ├── Dockerfile
│   │   ├── crontab
│   │   ├── docker-compose.yml
│   │   ├── entrypoint.sh
│   │   └── qiita.sh
│   └── docker_compose_up.sh
└── app
     ├── __init__.py
     ├── alembic.ini
     ├── config
     │   └── qiita_access_token
     ├── main.py
     ├── migration
     │   ├── README
     │   ├── env.py
     │   ├── script.py.mako
     │   └── versions
     │       └── 62fb1bcf0a8a_create_table.py
     ├── model
     │   ├── __init__.py
     │   └── qiita.py
     └── requirements.txt

Hands-on

herunterladen

command.sh


~% git clone https://github.com/nsuhara/python-docker-cron.git -b master

Servicestart

command.sh


~% cd python-docker-cron/
~% sh Dockerfiles/docker_compose_up.sh

Serviceende

command.sh


~% Control Key + C

Python Script

tree.sh


/
└── app
     ├── main.py
     └── model
          ├── __init__.py
          └── qiita.py

Modellerstellung

qiita.py


"""app/model/qiita.py
"""
from datetime import datetime, timezone

from sqlalchemy import Column
from sqlalchemy.dialects import postgresql as db
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Qiita(Base):
    """Qiita
    """
    __tablename__ = 'qiita'

    id = Column(db.INTEGER, primary_key=True, autoincrement=True)
    date = Column(db.DATE, nullable=False)
    title = Column(db.VARCHAR(255), nullable=False)
    page_views_count = Column(db.INTEGER, nullable=False)
    likes_count = Column(db.INTEGER, nullable=False)
    url = Column(db.VARCHAR(255), nullable=False)
    created_at = Column(db.TIMESTAMP, nullable=False)

    def __init__(self, date, title, page_views_count, likes_count, url):
        self.date = date
        self.title = title
        self.page_views_count = page_views_count
        self.likes_count = likes_count
        self.url = url
        self.created_at = datetime.now(timezone.utc)

    def to_dict(self):
        """to_dict
        """
        return {
            'id': self.id,
            'date': self.date,
            'title': self.title,
            'page_views_count': self.page_views_count,
            'likes_count': self.likes_count,
            'url': self.url,
            'created_at': self.created_at
        }

Hauptkreation

main.py


"""app/main.py
"""
import os
import sys
from datetime import datetime

import requests
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from model.qiita import Qiita


def get(url):
    """get
    """
    qiita_access_token = os.getenv('QIITA_ACCESS_TOKEN', '')
    if qiita_access_token == '':
        print('Error: please set \'QIITA_ACCESS_TOKEN\' to environment variable.')
        sys.exit()

    headers = {
        'Content-Type': 'application/json',
        'Charset': 'utf-8',
        'Authorization': 'Bearer {}'.format(qiita_access_token)
    }
    res = requests.get(url=url, headers=headers)
    print('{}, {}'.format(res.status_code, res.url))
    return res


def get_id_list():
    """get_list
    """
    res = get(url='https://{url}?page={page}&per_page={per_page}'.format(**{
        'url': os.getenv('QIITA_URL_LIST'),
        'page': os.getenv('QIITA_PAGE'),
        'per_page': os.getenv('QIITA_PER_PAGE')
    }))
    return [item.get('id') for item in res.json()]


def get_item(qiita_id):
    """get_item
    """
    res = get(url='https://{url}/{id}'.format(**{
        'url': os.getenv('QIITA_URL_ITEM'),
        'id': qiita_id
    }))
    item = res.json()
    return {
        'page_views_count': item.get('page_views_count'),
        'likes_count': item.get('likes_count'),
        'title': item.get('title'),
        'url': item.get('url')
    }


def output_db(items):
    """output_db
    """
    engine = create_engine(
        os.getenv('SQLALCHEMY_DATABASE_URI', ''),
        echo=bool(int(os.getenv('SQLALCHEMY_ECHO', '0'))),
        pool_size=int(os.getenv("SQLALCHEMY_POOL_SIZE", '5')),
        max_overflow=int(os.getenv("SQLALCHEMY_MAX_OVERFLOW", '10')),
        pool_timeout=int(os.getenv("SQLALCHEMY_POOL_TIMEOUT", '30'))
    )
    create_session = sessionmaker(bind=engine)
    session = create_session()

    insert = list()
    date = datetime.now().date()

    session.query(Qiita).filter(Qiita.date == date).delete()

    for item in items:
        insert.append(Qiita(
            date=date,
            title=item.get('title'),
            page_views_count=item.get('page_views_count'),
            likes_count=item.get('likes_count'),
            url=item.get('url')
        ))

    session.add_all(insert)
    session.commit()


def output_log(items):
    """output_log
    """
    total_page_views_count = 0
    total_likes_count = 0

    print('-'*100)

    for item in items:
        total_page_views_count = total_page_views_count + \
            item.get('page_views_count')
        total_likes_count = total_likes_count+item.get('likes_count')

        print('page_views_count={}, likes_count={}, title={}, url={}'.format(
            str(item.get('page_views_count')).zfill(5),
            str(item.get('likes_count')).zfill(2),
            item.get('title'),
            item.get('url'))
        )

    print('\nitems_count={}, total_page_views_count={}, total_likes_count={}'.format(
        len(items), total_page_views_count, total_likes_count))

    print('-'*100)


def main():
    """main
    """
    items = list()

    for qiita_id in get_id_list():
        items.append(get_item(qiita_id=qiita_id))

    sorted_items = sorted(
        items, key=lambda x: x['page_views_count'], reverse=True)

    if os.getenv('OUTPUT', '') == 'output_db':
        output_db(items=sorted_items)
    else:
        output_log(items=sorted_items)


if __name__ == '__main__':
    main()

Qiita-Zugriffstokeneinstellung

command.sh


~% echo "export QIITA_ACCESS_TOKEN={qiita access token}" > app/config/qiita_access_token

--Hinweis: [Zugriffstoken](https://qiita.com/nsuhara/items/b27b84f0150c3f6534ec#%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83 % 88% E3% 83% BC% E3% 82% AF% E3% 83% B3)

Database Migration

tree.sh


/
└── app
     ├── alembic.ini
     └── migration
          ├── README
          ├── env.py
          ├── script.py.mako
          └── versions
              └── 62fb1bcf0a8a_create_table.py

Alembic Installation

command.sh


~% pip install alembic

Alembic-Initialisierung

command.sh


~% alembic init {directory}

Alembic-Einstellungen

command.sh


~% vim app/alembic.ini

command.sh


- sqlalchemy.url = driver://user:pass@localhost/dbname
+ sqlalchemy.url = postgresql+psycopg2://postgres:postgres@docker_db:5432/qiita

Skripterstellung

command.sh


~% alembic revision -m 'Create Table'

--Hinweis: app / Migration / Versionen / {Revisions-ID} _create_table.py wird generiert

Skriptbearbeitung

create_table.py


"""Create Table
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql as db

# revision identifiers, used by Alembic.
revision = '62fb1bcf0a8a'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    op.create_table(
        'qiita',
        sa.Column('id', db.INTEGER, primary_key=True),
        sa.Column('date', db.DATE, nullable=False),
        sa.Column('title', db.VARCHAR(255), nullable=False),
        sa.Column('page_views_count', db.INTEGER, nullable=False),
        sa.Column('likes_count', db.INTEGER, nullable=False),
        sa.Column('url', db.VARCHAR(255), nullable=False),
        sa.Column('created_at', db.TIMESTAMP, nullable=False),
    )


def downgrade():
    pass

Manuelle Tabellengenerierung

command.sh


~% python app/migration/versions/{revision id}_create_table.py

Docker Compose

tree.sh


/
├── Dockerfiles
│   ├── app
│   │   ├── Dockerfile
│   │   ├── crontab
│   │   ├── docker-compose.yml
│   │   ├── entrypoint.sh
│   │   └── qiita.sh
│   └── docker_compose_up.sh
└── app

docker_compose_up.sh

docker_compose_up.sh


#!/bin/sh

docker stop $(docker ps -q)
docker rm $(docker ps -q -a)
# docker rmi $(docker images -q) -f

rsync -av --exclude=app/tests* app Dockerfiles/app
docker-compose -f Dockerfiles/app/docker-compose.yml up --build

docker-compose.yml

docker-compose.yml


version: '3'
services:
  docker_db:
    image: postgres:11.5
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: 'postgres'
      POSTGRES_PASSWORD: 'postgres'
      POSTGRES_DB: 'qiita'
      POSTGRES_INITDB_ARGS: '--encoding=UTF-8'

  app:
    build: .
    depends_on:
      - docker_db
    volumes:
      - ./crontab:/var/spool/cron/crontabs/root
    environment:
      PYTHONPATH: '/code/'
      TZ: 'Asia/Tokyo'
      QIITA_PAGE: '1'
      QIITA_PER_PAGE: '100'
      QIITA_URL_ITEM: 'qiita.com/api/v2/items'
      QIITA_URL_LIST: 'qiita.com/api/v2/authenticated_user/items'
      OUTPUT: 'output_db'
      SQLALCHEMY_DATABASE_URI: 'postgresql+psycopg2://postgres:postgres@docker_db:5432/qiita'
      SQLALCHEMY_ECHO: '1'
      SQLALCHEMY_POOL_SIZE: '5'
      SQLALCHEMY_MAX_OVERFLOW: '10'
      SQLALCHEMY_POOL_TIMEOUT: '30'

Dockerfile

FROM python:3.7

RUN mkdir /code
WORKDIR /code

ADD entrypoint.sh /code/entrypoint.sh
ADD qiita.sh /code/qiita.sh
ADD app/ /code

RUN export -p
RUN chmod +x /code/entrypoint.sh
RUN chmod +x /code/qiita.sh

RUN apt-get update && apt-get -y install busybox-static

RUN pip install --upgrade pip --no-cache-dir
RUN pip install -r requirements.txt --no-cache-dir

RUN touch /var/log/cron.log

# EXPOSE 0000

CMD ["/code/entrypoint.sh"]

entrypoint.sh

entrypoint.sh


#!/bin/bash

sleep 5
# set qiita access token to environment variable
source /code/config/qiita_access_token
# migrate database
alembic upgrade head
# run script at 23:00, 0 23 * * * /code/qiita.sh  >> /var/log/cron.log 2>&1
busybox crond -l 8 -L /dev/stderr -f

crontab

0 23 * * * /code/qiita.sh  >> /var/log/cron.log 2>&1

qiita.sh

qiita.sh


#!/bin/sh

python /code/main.py

Starten Sie verschiedene Dienste

command.sh


~% sh Dockerfiles/docker_compose_up.sh

Melden Sie sich lokal beim Docker-Container an

command.sh


docker ps
# CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
# 0af68012c7c9        app_app             "/code/entrypoint.sh"    38 seconds ago      Up 37 seconds       0.0.0.0:8000->8000/tcp   app_app_1
# 9901a105e8d5        postgres:11.5       "docker-entrypoint.s…"   39 seconds ago      Up 37 seconds       0.0.0.0:5432->5432/tcp   app_postgres_1

docker exec -i -t {CONTAINER ID} /bin/bash

Melden Sie sich lokal bei Docker-PostgreSQL an

command.sh


~% psql -h localhost -p 5432 -d qiita -U postgres

Bonus: Heroku

Erstellen Sie runtime.txt

Supported runtimes

command.sh


~% cd python-docker-cron/
~% echo python-3.7.9 > app/runtime.txt

Heroku Postgres Registrierung

  1. {App-Name}> Registerkarte "Ressourcen"> Geben Sie "Heroku Postgres" in die Suche nach Add-Ons> Schaltfläche "Bereitstellung" ein und wählen Sie sie aus

Einstellungen für Umgebungsvariablen

  1. {App-Name}> Registerkarte "Einstellungen"> Schaltfläche "Config Vars anzeigen"> Umgebungsvariablen registrieren

Bearbeiten Sie alembic.ini

command.sh


~% cd python-docker-cron/
~% vim app/alembic.ini

command.sh


- sqlalchemy.url = postgresql+psycopg2://postgres:postgres@docker_db:5432/qiita
+ sqlalchemy.url = {SQLALCHEMY_DATABASE_Gleiche URL wie URI}

Bereitstellen

command.sh


~% cd python-docker-cron/app/
~% git init
~% git commit -am "make it better"
~% heroku login
~% heroku git:remote -a {app-name}
~% git push heroku master

Heroku Postgres Migration

command.sh


~% cd python-docker-cron/
~% heroku run python migration/versions/62fb1bcf0a8a_create_table.py

or

command.sh


~% heroku pg:psql {database} --app {app-name}

command.sh


{app-name}::DATABASE=> CREATE TABLE qiita (
    id SERIAL NOT NULL,
    date date not null,
    title VARCHAR(255) NOT NULL,
    page_views_count Integer NOT NULL,
    likes_count Integer NOT NULL,
    url VARCHAR(255) NOT NULL,
    created_at TIMESTAMP NOT NULL,
    PRIMARY KEY (id)
);

Heroku Scheduler Registrierung

  1. {App-Name}> Registerkarte Ressourcen> Nach Add-Ons suchen und "Heroku Scheduler"> "Bereitstellung" auswählen
  2. Heroku Scheduler> Schaltfläche "Job hinzufügen"
  3. Stellen Sie den Ausführungszyklus auf "Zeitplan"
  4. Setzen Sie python main.py auf Run Command

Recommended Posts

Führen Sie regelmäßig Python Script mit Docker Compose + Daemon (cron) aus.
Verwenden Sie GDAL mit Python mit Docker
Erstellen Sie die Node.js-Umgebung mit Docker Compose
WordPress mit Docker Compose unter CentOS 8
[Docker] [18116] Fehler beim Ausführen der Traceback-Auflösung für Skript-Docker-Compose