LoginSignup
2
3

More than 1 year has passed since last update.

Heroku上のPythonからRedisを使う 開発環境Mac

Last updated at Posted at 2022-11-14

Redis to Goがadd onから削除されたことによってしばらく混乱していたのですが、やっとすっきり解決したので備忘録として公開。

何が書いてあるか

  • Heroku上のPythonからHeroku Data for Redisを使うための方法が書いてある
  • Macのローカルで同じ環境を実現して開発する方法が書いてある

Let's go

HerokuのPythonからバックグラウンドタスクを処理しようとする場合のオフィシャルドキュメントはこちら

ただし、これはRedis to Goがadd_onに存在していたときの情報で、若干変わっているので英語版を見る必要がある。

そして、worker.py にはこう書けと書いてあるのだが、それだと動かない

python worker.py
redis_url = os.getenv('REDIS_URL', 'redis://localhost:6379')

conn = redis.from_url(redis_url)

Heroku Data for Redisのプランがhobbyの場合は問題ないのだが、課金すると突然動かなくなる。
なぜかというとTLSが必要になるから。
これについてはここに書いてある

worker.py
import os
from urllib.parse import urlparse
import redis

url = urlparse(os.environ.get("REDIS_URL"))
r = redis.Redis(host=url.hostname, port=url.port, username=url.username, password=url.password, ssl=True, ssl_cert_reqs=None)

そして、何故かこのままだと動かない。urlparseでportがうまく認識されないっぽい。謎。
ということで、最終的にはhost,port,passを環境変数に入れることになった。

上記の原因はusernameが含まれていたからだった。
Heroku Data for Redisを追加してそのURLを見ると分かるのだがusernameは存在しないのだ。
ということで、以下の様にするのが正解であった。元々はhost,pass,portをそれぞれ環境変数に入れてそれを使う形にしていたのだが、Heroku Data for Redisは定期的にURLが変更される。そのタイミングで使えなくなってしまうので、設定は以下のようにしましょう。

2023.1.12追記 ドキュメントページに修正を反映してもらいました
https://devcenter.heroku.com/articles/connecting-heroku-redis#connecting-in-python

worker.py
import os
import redis
from urllib.parse import urlparse

from rq import Worker, Queue, Connection
import logging
logger = logging.getLogger(__name__)

listen = ["events", 'cursor', 'high', 'default', 'low']

if os.environ.get("ENV") != 'production':
    print("development")
    conn = redis.Redis(host='localhost', port=6379, db=0)
else:
    url = urlparse(os.environ.get("REDIS_URL"))
    conn = redis.Redis(
        host=url.hostname,
        port=url.port,
        password=url.password,
        ssl=True,
        ssl_cert_reqs=None
        )


if __name__ == '__main__':
    with Connection(conn):
        print("worker start")
        worker = Worker(map(Queue, listen))
        worker.work()

これらをHerokuの環境変数に入れておく

.env
export REDIS_HOST=hostのURL
export REDIS_PORT=REDISのポート番号
export REDIS_PASS=REDISのパスワード

if os.environ.get("ENV") != 'production':
これは、ローカル開発環境で使うための分岐。
MacにRedisインストールして、ターミナルから
$ redis-server
するとRedisが立ち上がる。
ここに接続する場合は、環境変数ENVを
export ENV=local
としておけば
conn = redis.Redis(host='localhost', port=6379, db=0)
を使ってローカルのRedisに接続される

workerについて

作ったworker.pyをどこに置くかというと、ドキュメントルートである。
ProcFileにはこんなふうに書く

worker: python ./worker.py

実は、かねてよりここに書いてある形でSlack用のアプリケーションを書いている

ここの構成では、Pythonで動く部分は
/backend/
以下にファイルを配置しており、worker.pyももれなくそれに従う形で配置していた
/backend/worker.py
そして、ProcFileには

worker: python ./backend/worker.py

と書いていたのだけど、これだとパスが通らない。workerはドキュメントルートに配置しましょう。そうしないと、workerから呼び出す関数へのパスがおかしくなるので実行できないのです。

2種類のworkerを使いたい

処理が異なる2つのバックグラウンドタスクが存在しており、片方が割と重たいけど常に使われているものではない、且つもう一方は常時稼働が必要なタスクなのでそちらに影響を与えたくないということで2つのworkerを設置することにしました。
最初一つのworkerでやろうとしたら、重たい方を回したら、常時稼働が必要な方が止まりました。という訳で分離しましょうということにしました。

  • workerのファイルをコピーして若干変更
  • Redisももう一つadd on追加して2つ起動するように変更
workerc.py
import os
import redis
from urllib.parse import urlparse

from rq import Worker, Queue, Connection
import logging
logger = logging.getLogger(__name__)

listen = ["events", 'cursor', 'high', 'default', 'low']

if os.environ.get("ENV") != 'production':
    print("development")
    conn_c = redis.Redis(host='localhost', port=6379, db=0)
else:
    url = urlparse(os.environ.get(os.environ.get("REDIS_COLLECTOR_KEY")))
    conn_c = redis.Redis(
        host=url.hostname,
        port=url.port,
        password=url.password,
        ssl=True,
        ssl_cert_reqs=None
        )

if __name__ == '__main__':
    with Connection(conn_c):
        worker = Worker(map(Queue, listen))
        worker.work()

ほぼ同じですが、Redisの接続先をもう一つの方に変更しています。
環境変数を追加しておきます

  • REDIS_C_HOST
  • REDIS_C_PORT
  • REDIS_C_PASS

ProcFileにも一行追加しています

worker: python ./worker.py
collect: python ./workerc.py

pythonから呼び出す形はこんな感じ

bolt.py
from rq import Queue
from worker import conn
from workerc import conn_c
from .collect import start_collect
q = Queue(connection=conn,default_timeout=100)
c = Queue(connection=conn_c,default_timeout=1000000000)
@bolt_app.view("collect_past_posts_submit")
def collect_past_posts_submit(ack, body, logger, client, view, context):
    ack()
    result = c.enqueue(start_collect, body['team']['id'], context['user_id'])

@bolt_app.event("message")
def handle_message_events(client, body, logger, context):
    user_id = context['user_id']
    try:
        userinfo = client.users_info(
            user=user_id,
            include_locale=True
        )
    except SlackApiError as e:
        print("Error fetching conversations: {}".format(e))
    post = q.enqueue(handleMessage,client,body,context,logger,userinfo,job_timeout=36000)

def handleMessage(client,body,context,logger,userinfo):
    print('handle_message_events ■□■□■□■□■□■□■□■□■□■□■□')
    #何かしらの処理----

ローカルでRedis2つ立ち上げる方法までは調べなかったので、ローカルでも同じ環境にしたいという場合はもう少し調べる必要があります。
一つのRedisに2つのworkerで接続しようとすると、処理が2つに別れないですが、検証にはそれほど困らないと思う。

Herokuのworkerのログを見るには?

以下のコマンドで見ることができます

heroku logs -t -d worker -a アプリ名
2つ目の方は
heroku logs -t -d collect -a アプリ名
となります。ProcFileで指定した名前で呼び出す形式です。

heroku logs -t -a アプリ名
とすると、アプリ内の全dynoのログが確認できます。
処理がきちんと指定したworkerに伝わっているかどうかを確認するためには-d オプションを付けてworkerのログを見る必要があります。

コンソールから接続するには

heroku redis:cli -a アプリ名
という形で接続します。
一つのアプリに複数のREDISインスタンスを紐付けている場合だとこれだと接続できませんので
heroku redis:cli -a アプリ名 HEROKU_REDIS_xxxx
というように、REDISを追加した際につけられた名前を指定してアクセスしましょう

Redisにログインして作業する

keyを確認するには

>keys *
これですべてのキーを確認できます。

キーを削除する場合

>flushdb

TTLが設定されていないキーの取得

>info keyspace

以上

開発してるSlackアプリの紹介

よろしければインストールして使ってみて下さい!こちらのTIPSを利用しているのはTimeLineです。

TASUKARU

Slackで色々な人からメンションをもらって混乱したことはありませんか?
何か頼まれていたはずだけど思い出せない… そんな状態からあなたを救います。
TASUKARUはSlack専用のタスクマネージャーです。
デフォルトのスレッドやメンション画面では、処理が終わったpostをアーカイブすることが出来ませんが、TASUKARUならそれが出来ます。
今アクションが必要なものだけに集中することができる、それがTASUKARUです。 https://lne.st/3x1u

TimeLine for Slack

全ての公開チャンネルのpostを一つのチャンネルにまとめるタイムラインチャンネルを生成するアプリです。
一部のチャンネルのみを集めたミックスチャンネルを作成すると、自分が必要なチャンネルだけのタイムラインを作り出すことが出来ます。
オプション機能として、メッセージ転送時にDeepL翻訳を挟むことが出来ます。 https://lne.st/wr21

OYASUMI bot

Googleカレンダーのスケジュールに「休み」等の、休暇判定キーワードが入っていた場合に、自動的にSlackをスヌーズ状態に変更します。
メンションがあった場合に、メンションした相手にその日は休暇ですというレスを付けます。
スタッフの休暇を気持ちよく過ごしてもらう為のアプリです。 https://lne.st/oyasumilink

TIPS

Slack用のリマインダーアプリです。
デフォルト機能のリマインダーは、一つのスケジュールに一つのメッセージの設定しか出来ません。
このアプリでは、一つのスケジュールに複数のメッセージを登録し、ランダムで指定された時間にpostします。
チャンネル特有のノウハウ等をスケジュール登録しておくことで、自然と情報が浸透する状態を作ります。 https://lne.st/tips

YOKOKU for Slack

OYASUMI botの応用です。
例えば「営業」というキーワードが入った予定だけを抽出したいと思ったことはありませんか?
カレンダーをいちいち検索するのは手間がかかります。
このアプリを用いれば、特定のキーワードが入った予定を、特定のチャンネルに流すことが出来ます。 https://lne.st/4cdy

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3