やる気がストロングZERO

やる気のストロングスタイル

Railsの開発環境とステージング環境をDockerを使って構築したのでメモ

Railsの開発環境とステージング環境をDockerを使って構築してみた。
注意:Docker周りインフラ周りについてエキスパートではないので本当にこれが要求を満たせているのかは不明

ディレクトリ構成

.
├── app
│   ├── Dockerfile  # webサーバー構築用
│   ├── conf.d 
│   │   └── rails_app.conf  # nginxの設定ファイル
│   ├── rails_app/  # railsアプリのルートディレクトリ
├── db
│   ├── data/  # dbの実データのマウントディレクトリ
│   └── db_init.sql  # db初期化時に実行するスクリプト(dbとuserの作成)
└── docker-compose.yml

./docker-compose.yml

version: '3'

services:
  app:
    build: ./app/
    volumes:
      - ./app/log/nginx:/var/log/nginx
      - ./app/conf.d:/etc/nginx/conf.d
      - ./app/rails_app:/app
    ports:
      - "80:80"
    depends_on:
      - db
  db:
    image: postgres
    ports:
      - "5432:5432"
    volumes:
      - ./db/data/data:/var/lib/postgresql/data
      - ./db/db_init.sql:/docker-entrypoint-initdb.d/db_init.sql

・DBサーバーとWEBサーバーを使ってrailsアプリを動作させるための設定
・./app/DockerfileにNginxとRubyrailsのインストールと起動コマンドが記載されている
・volumesのマウント系はDockerfileに書こうとすると絶対パスで書かないといけない?っぽかったのでこちらに書いた。(どちらになにを書くべきなのか理解がまだあやふや)
・現状はrailsアプリコードをvolumeでマウントするようにしているが、railsが生成するキャッシュファイルなどがroot権限で作成されてしまい、ホスト側から触りづらいので、マウントではなくcopyやaddなのでイメージに含めてしまうのがいいのかも。(で、キャッシュファイルなどを保存したければ個別にvolumeマウント設定をしておく)

・- ./db/data/data:/var/lib/postgresql/data
DBのvolumeマウントについて、dockerが管理するvolumeに任せることもできるみたいだったが、ディレクトリ内にファイルとして存在してくれていたほうが管理しやすいと思っているのでプロジェクトディレクトリ内にマウントする。

・- ./db/db_init.sql:/docker-entrypoint-initdb.d/db_init.sql
postgresqlの初期化時にdb_init.sqlを実行してもらう設定。
db_init.sqlの内容は、railsで使用するDBとuserを作成するsqlが書かれている。

./db/init_db.sql

railsで使用するDBとuserを作成するsqlが書かれている。
・fixtureの実行時にSUPERUSERであることが求められたのでSUPERUSERにした。

create role rails_app_dev login password 'rails_app_dev';
ALTER USER rails_app_dev WITH SUPERUSER;
create role rails_app_prod login password 'rails_app_prod';
ALTER USER rails_app_prod WITH SUPERUSER;

create database rails_app_dev;
ALTER DATABASE rails_app_dev OWNER TO rails_app_dev;

create database rails_app_prod;
ALTER DATABASE rails_app_prod OWNER TO rails_app_prod;

create database rails_app_test;
ALTER DATABASE rails_app_test OWNER TO rails_app_dev;

./app/Dockerfile

・NginxとRubyrailsのインストールと起動コマンドの設定。 ・bundle installをCMDでやっているがここでやるべきなのかわからないがとりあえず動くようだ

FROM ubuntu:18.04


WORKDIR /root


# ロケール設定
ENV LC_ALL C.UTF-8
ENV LANG C.UTF-8


# shellをbashに指定
SHELL ["/bin/bash", "-c"]


# 最新状態へ
RUN apt update
RUN apt -y upgrade


# 必要なコマンドインストール
RUN apt -y install build-essential wget zlib1g-dev libssl-dev libbz2-dev libreadline-dev libpq-dev


# nginxのインストール
RUN apt -y install nginx


# Rubyのインストール
RUN wget https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.1.tar.gz && \
tar zxvf ruby-2.6.1.tar.gz && \
cd ruby-2.6.1 && \
./configure && \
make && \
make install


# railsのインストール
RUN gem install rails


WORKDIR /app
EXPOSE 80
CMD (bundle install && rails assets:precompile RAILS_ENV=production && rails s -e production) & nginx -g "daemon off;"

./app/conf.d/rails_app.conf

・Nginxからpumaへリクエストが通るように設定

# 書籍 Ruby on Rails5 アプリケーションプログラミングを参考に記載。
upstream rails_app {
  server localhost:3000;
}


server {
  listen 80;
  server_name localhost;
  root /app/public;


  location / {
    try_files $uri @rails_app;
  }


  location @rails_app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://rails_app;
  }


  error_page 500 502 503 504 /500.html;
}

./app/rails_app/config/database.yml

railsのDB設定

default: &default
  adapter: postgresql
  encoding: unicode
  port: 5432
  # For details on connection pooling, see Rails configuration guide
  # http://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>


development:
  <<: *default
  host: localhost
  database: rails_app_dev
  username: rails_app_dev
  password: rails_app_dev


test:
  <<: *default
  host: localhost
  database: rails_app_test
  username: rails_app_dev
  password: rails_app_dev


production:
  <<: *default
  host: db
  database: rails_app_prod
  username: rails_app_prod
  password: rails_app_prod

seedなどの設定

割愛するが、本来なら再設定が必要なのかも(デフォルトでもインストール毎に違う値が初期設定されているっぽかったが)

運用方法

開発時

'・docker-compose up -d db'でDBサーバーコンテナを起動しておく。
rails_app/以下のコードを編集し、rails serverコマンドでテスト実行する。(localhost:3000へアクセス)

ステージング環境テスト時

docker-composer up -d でWEBサーバーコンテナとDBサーバーコンテナを起動し localhost:80で確認する

【Django】ロギング設定について

いまいち使い方・設定の仕方がよくわからなかったので色々しらべた。
現状の知識をメモっとく。

Djangoで使われているロギング機構は、 別にDjango独自の機能ではなく、pythonの標準で用意されているloggingが使用されているという事のよう。

なので、特にDjangoでしか通用しない知識ではないが、 ここではDjangoでの設定の仕方をベースに記載する。

ロガーの設置方法

ロギングで必要なのは
(1)ログ出力したい部分にメッセージを指定
(2)指定したメッセージをどのように出力するか設定
の2つ。

(1)ログ出力したい部分にメッセージを指定

ライブラリ内やロジック内など要所要所に埋め込む。
例)

# このようにすることで、logger毎に異なるログ出力設定を指定できるようになる。
logger = logging.getLogger(__name__)

logger.debut("debugメッセージ")
logger.warning("warningメッセージ")
logger.info("infoメッセージ")
logger.error("errorメッセージ")
logger.critical("criticalメッセージ")

# ログ出力設定で指定されるログレベルによって、出力されるかどうかが変わる。
# 上記の場合、levelがERRORで指定された場合、logger.error(),logger.critical()のメッセージのみ出力される

(2)指定したメッセージをどのように出力するか設定

(1)で埋め込まれているメッセージをどのように出力するか(コンソールか、ファイルか等)の設定を行う。
Djangoでは、settings.py内でLOGGING変数に設定ディクショナリを設定する。

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'require_debug_false': {
             # ↓このあたりの書式についてまだよくわかってない。
            '()': 'django.utils.log.RequireDebugFalse',
        },
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    # 出力形式を指定できる。下記のhandlersの設定に渡して使う。
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
    },
    #  どこに出力するなどが設定できる。下記のrootやloggers設定に渡して使う。
    'handlers': {
        # ↓これは、DEBUGレベルでStreamHandler(標準出力)に、verboseフォーマッタの形式で出力するという意味
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'file_images_importer': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/app/sync/logs/import_twitter_images.log',
            'formatter': 'verbose',
        },
        'file_django_log': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': '/app/sync/logs/django.log',
            'formatter': 'verbose',
        },
    },
    'root': {
        'handlers': ['console', 'file_django_log'],  # ハンドラは複数渡せる。
        'level': 'INFO',
    }
}

ライブラリ毎に設定を変えたければ、'loggers'キーに設定を追加する

    'loggers': {                                                                                                                    
        'django': {                                                                                                                 
            'handlers': ['console', 'mail_admins', 'file'],                                                                         
            'level': 'INFO',                                                                                                        
        },                                                                                                                          
        'py.warnings': {                                                                                                            
            'handlers': ['console', 'file'],                                                                                        
        },                                                                                                                          
        'django.db.backends': {                                                                                                     
            'handlers': ['console'],                                                                                                
            'level': 'DEBUG',                                                                                                       
        },     

webサーバーのファイルのパーミッション設定はどうするのがいいのか考えた

今まであまり意識して設定していなかったので考えた。

※もともとeveryoneの権限設定を使ってwebサーバーにアクセスさせるように考えていたが、 ツッコミを受けて修正した。※ありがとうございます

開発者がログインする開発者ユーザー(「devuser」ユーザー)を作成して開発者全員で共有して使う。
devuserはwebサーバー用のグループwww-dataに追加する。
※それぞれ個別にユーザーを作成する方法も検討してみたが不要に複雑になりそうだったのでやめた。

apacheなどwebサーバー用のユーザー(www-dataユーザーなど)が読み取れるように

-rw-r--r-- devuser:www-data

というパーミッションにする。

キャッシュファイルなどwww-dataにも書き込み権限が必要な場合

-rw-rw-r-- devuser:www-data

というパーミッションにする。
これで、開発者全員編集可能で、webサーバープロセスからも適宜ファイルへのアクセスが可能になる。

pyenvでインストールしたpythonのpipでインストールしたuwsgiをwebサーバーで使用するための設定

webサーバー環境で実行されるuwsgiを、pyenvで入れたpythonのpipでインストールしたuwsgiにするにはどうすればいいのか考えた。

pyenvでインストールされたpython関連のコマンドを使うには予め

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

を実行しておかなければならない。

通常はbashrcに記載しておいてログイン時に自動的に実行されるようにしているが、uwsgiの自動起動の場合にはどうすればよいのか?

結論

・pyenvをrootユーザーでインストールし、rootユーザーのbashrcに上記pyenv用のコマンドを追記する
・systemdからuwsgiが起動されるように設定する(Systemd — uWSGI 2.0 documentation

上記結論に至るまでの流れ

・uwsgiはどうやって起動時に実行すればいいのか?
-> systemdによってubuntu起動時に起動させるということを知った。

・uwsgiはどのユーザーで起動されるのか
-> systemdを使った場合rootユーザーで起動されていることを確認した。
(子プロセスは明示的に指定可能)

そうであれば、rootユーザーのbashrcにpyenvの起動コマンドを記述しておけばいいのではないか?
という結論に至った。

ただし、必要なものはpyenvなどを使うのではなく通常通りにシステムにインストールしたほうがよさそう

上記の手法を使ってpyenvのpythonを使用する場合、rootユーザーなら問題ないがその他のユーザーでは実行できない。 pyenvは/root/.pyenv/に配置されるが、このディレクトリはrootユーザーでしか読み書きできない。

uwsgiでは--uidオプションで実行ユーザーを指定することができるが、仮にroot以外のユーザーで実行した場合にエラーとなってしまう。
解決するにはパーミッションを設定してやらないといけないが、そもそも本番環境でpyenvを使おうという考えが間違っているっぽい。

【Django】Custom Command と ArgumentParserの使い方メモ

DjangoでCustom commandの作り方とその時使うArgumentParserの使い方のざっくりメモ

参考)
カスタム django-admin コマンドの実装 | Django documentation | Django
ArgumentParserの使い方を簡単にまとめた - Qiita

app_dir/management/commands/以下にファイルを配置する。

from django.core.management.base import BaseCommand, CommandError

# BaseCommandを継承したCommandクラスを定義する  
class Command(BaseCommand): 
    help = '-hでヘルプ表示させるときに表示される部分。コマンドの概要などを記入'

    def add_arguments(self, parser): #コマンド引数の定義
        
        # int型の必須引数の定義
        parser.add_argument('id', type=int, help='なんかidを指定してください')

        # str型のオプション引数の定義。nargs='*'指定で、--option aa bb ccという感じに複数指定できる。
        # nargs='+'とすると、引数は1つ以上指定しないとエラーになる。
        parser.add_argument('-o', '--option', type=str, nargs='*', help='なんかオプションを指定してください')

    def handle(self, *args, **options): # コマンド処理を定義する

         #options[引数名]で引数を取得できる。
        self.stdout.write(self.style.SUCCESS('id = "%s"' % options['id']))

        # nargsを指定した引数はコレクションで入っている。
        for option in options['option']: 
            self.stdout.write(self.style.SUCCESS('option = "%s"' % option))

ターミナル環境(zsh)の設定メモ

Ubuntuを開発環境にするにあたり、ターミナル環境の整備を行っている。
ググりながら良さげな設定をチョイス中。
知識不足でなかなか情報どおりの設定ができなかったりするが、少しずつ理解しながら設定していく。

zshインストール

zshをインストール

sudo apt install zsh

ログインシェルをzshにする 参考(ログインシェルをzshに変える - Qiita

zshrcなどの設定ファイルは「zshの本」に記載されていたものをそのまま記載した。 (内容はまだ理解できていない。あとで一旦デフォルトに戻すかもしれない)
下記からコピペできた。
http://www.gentei.org/~yuuji/support/zsh/

peco関連の設定

参考)pecoる - Qiita

PECOのインストール

バイナリをダウンロードしてパスが通っている場所に配置した。 参考)ubuntuでのpecoのインストール - Qiita

コマンド履歴設定

.zshrcに追記

function peco-select-history() {
    # historyを番号なし、逆順、最初から表示。
    # 順番を保持して重複を削除。
    # カーソルの左側の文字列をクエリにしてpecoを起動
    # \nを改行に変換
    BUFFER="$(history -nr 1 | awk '!a[$0]++' | peco --query "$LBUFFER" | sed 's/\\n/\n/')"
    CURSOR=$#BUFFER             # カーソルを文末に移動
    zle -R -c                   # refresh
}
zle -N peco-select-history
bindkey '^R' peco-select-history

これで、
ctrl r でコマンド履歴が扱いやすくなった。

ghq設定

goをインストール

sudo apt install golang  

goのパス設定
.zshrcに追記

export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

ghqのインストール

go get github.com/motemen/ghq

githubにあった補完ファイル「_ghq」をfpathの通っているところに置く(補完が聞くようになる。)とりあえず一番先頭のところに置いた これでghq コマンドでgithubリポジトリを管理できるようになった。

cdr設定

.zshrcに下記追記

if [[ -n $(echo ${^fpath}/chpwd_recent_dirs(N)) && -n $(echo ${^fpath}/cdr(N)) ]]; then
    autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
    add-zsh-hook chpwd chpwd_recent_dirs
    zstyle ':completion:*' recent-dirs-insert both
    zstyle ':chpwd:*' recent-dirs-default true
    zstyle ':chpwd:*' recent-dirs-max 1000
    zstyle ':chpwd:*' recent-dirs-file "$HOME/.cache/chpwd-recent-dirs"
fi

function peco-cdr () {
    local selected_dir="$(cdr -l | sed 's/^[0-9]\+ \+//' | peco --prompt="cdr >" --query "$LBUFFER")"
    if [ -n "$selected_dir" ]; then
        BUFFER="cd ${selected_dir}"
        zle accept-line
    fi
}
zle -N peco-cdr
bindkey '^[r' peco-cdr

ctrl [ rでディレクトリ履歴操作できるようになった。

tigのインストール

gitツリーなどを見るためにtigをインストールした。
Git GUIクライアントツールからの脱却検討 - やる気がストロングZERO

tmux設定

便利そうなのでtmuxを入れた。

sudo apt install tmux

とりあえずほぼデフォルトだが、コピーモードでviの操作でキー操作できるようにして、ペインを開くときにカレントディレクトリを維持するようにした。 ~/.tmux.conf

set-window-option -g mode-keys vi
bind c new-window -c '#{pane_current_path}'
bind '"' split-window -c '#{pane_current_path}'
bind % split-window -h -c '#{pane_current_path}'

tmuxでよく使う操作

画面分割横
ctrl b + %

画面分割縦
ctrl b + "

ペイン移動
ctrl b + o

現在のペインの最大化(もう一度実行でもどる)
ctrl b + z

ウィンドウ追加
ctrl b + c

ウィンドウ一覧から選択
ctrl b + w

デタッチ
ctrl [ + d

セッション一覧
tmux ls

セッション指定でアタッチ
tmux attach -t [session番号]