- 投稿日:2020-10-21T21:33:30+09:00
GitHub Actionsユーザーは今すぐDocker Hubのpull回数制限に備えよう
発生する問題
2020年11月より、Docker Hubがログインしていないユーザーに対してPullの回数を制限します。というか、すでに段階的に始まっています。
GitHub Actionsについても、
toomanyrequests: Too Many Requests. Please see https://docs.docker.com/docker-hub/download-rate-limit/
というエラーでDockerイメージのpullに失敗し、ワークフローがエラーになりそうです(私はまだエラーになったことがない)
※ IPアドレスでのrate-limitのため、自分のpull回数に関わらずエラーが起こりえます。
対応策
公式アナウンス(英語)で紹介されている通り、docker/login-actionを使ってみましょう。
対応後のyaml
workflow.ymljobs: HogeHogeJob: name: HogeHogeJob runs-on: ubuntu-latest steps: # この5行を追加する - name: Login to DockerHub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} # あとは今まで通りで良い - name: Checkout uses: actions/checkout@v2 - name: Setup python uses: actions/setup-python@v2 with: python-version: 3.7
DOCKERHUB_USERNAME
とDOCKERHUB_TOKEN
は、あらかじめsecretsとしてGitHubに登録しておきましょう(公式Doc)注意点として、
DOCKERHUB_TOKEN
にはパスワードではなくAccess Tokenを使うことが推奨されています。パスワードでも動きますが、ちゃんとAccess Tokenを発行しましょう。Access Tokenの発行方法
- https://hub.docker.com/ にログイン
- 右上のユーザー名から Account Settings
- Security -> New Access Token -> あとは流れに乗る
発行されたAccess Tokenを
DOCKERHUB_TOKEN
として登録すれば完了。docker/login-actionでログインできるようになります。
- 投稿日:2020-10-21T20:43:58+09:00
DockerでSparkを動かしたらlocalhost:4040にアクセスできない件について
Sparkの
Spark Web UI
(localhost:4040)だけが何故か接続できなかったので、色々調べていたのですが解決策が乗っておらず、慣れていない人であれば同じ悩みを抱えている人がいると思ったので、初投稿します。また業務中に起きたエラーのため、コードなどは一切載せていませんのでご理解のほどよろしくお願いいたします。環境
DockerでPythonのコンテナとSparkのコンテナ(wokerを接続している)をBridgeしている
どちらの環境にもPyspark
、java
の環境がインストールされている。環境構築の参考
SparkのMaster1つとWorker1つをdocker-composeで上げてpysparkを動かすまで
https://qiita.com/hrkt/items/fe9b1162f7a08a07e812ゴール
PythonのDockerの中に入って(docker exec...) spark-submitを行い、処理を行っている間にlocalhost:4040に接続してWebUIを確認したい。
問題について
起動してからlocalhost:8080はつながるのになぜか4040は接続ができない
解決策
- Sparkが処理中時にのみlocalhost:4040にアクセスできる
- docker-compose.ymlのSparkのコンテナにport 4040:4040と書いていたが、Job管理をするPython側にport 4040:4040を書く必要があった。(今回はこれが該当)
まとめ
StackOverFlowなどを読んでも全く分からなかったが、先輩に聞いたら一発で解決した。インフラにもっと強くなりたいです。
おまけ
sparkの中でサンプルコードを動かしていたが、
df.show()
すると
initial job has not accepted any resources; check your cluster ui to ensure that workers are registered and have sufficient resources
と出て止まってしまう。リソースが足りないとのことで、メモリ設定などを変えたが解決しなかった。サンプルコードはこちらから借りてきました。
PySpark のスクリプトファイルで引数を扱う
https://blog.amedama.jp/entry/2018/03/17/113516参考にしたサンプルコード
from pyspark import SparkConf from pyspark import SparkContext from pyspark.sql import SparkSession def main(): conf = SparkConf() conf.setAppName('example') sc = SparkContext(conf=conf) spark = SparkSession(sc) df = spark.sql('SELECT "Hello, World!" AS message') df.show()解決策
conf = SparkConf().setMaster('local')
- 投稿日:2020-10-21T18:56:50+09:00
nginx + expressをfargate 4coreで動かす
- nginx + expressをfargateに詰め込む
- unix socket を使って繋ぐ
- expressは4プロセス立ち上げる
- node clusterは使わないパターン、socketが競合してしまったので。
- 共通ボリュームにapi1.sock - api4.sockを設定して、nginxからupstreamする
api
共通ボリュームをVolumeにしておく
VOLUME /usr/src/tmpsockのパスを環境変数で指定させる
main.jsconst sock = process.env.SOCK_PATH; app.listen(sock, () => { fs.chmodSync(sock, '666'); console.log(`Server running on ${sock} with pid ` + process.pid); });nginx
api1.sock - api4.sockへupstreamする
nginx.confupstream api { server unix:///usr/src/tmp/api_1.sock; server unix:///usr/src/tmp/api_2.sock; server unix:///usr/src/tmp/api_3.sock; server unix:///usr/src/tmp/api_4.sock; } server { listen 80 default; listen [::]:80; location / { proxy_pass http://api; break; } }タスク定義
ECSタスク定義で、繋ぐ
task-define.json{ "containerDefinitions": [ { "name": "nginx", "portMappings": [ { "hostPort": 80, "protocol": "tcp", "containerPort": 80 } ], "mountPoints": [ { "readOnly": true, "containerPath": "/usr/src/tmp", "sourceVolume": "etc" } ] }, { "name": "api1", "mountPoints": [ { "containerPath": "/usr/src/tmp", "sourceVolume": "etc" } ], "environment": [ { "name": "SOCK_PATH", "value": "/usr/src/tmp/api_1.sock" } ] }, { "name": "api2", "mountPoints": [ { "containerPath": "/usr/src/tmp", "sourceVolume": "etc" } ], "environment": [ { "name": "SOCK_PATH", "value": "/usr/src/tmp/api_2.sock" } ] }, { "name": "api3", "mountPoints": [ { "containerPath": "/usr/src/tmp", "sourceVolume": "etc" } ], "environment": [ { "name": "SOCK_PATH", "value": "/usr/src/tmp/api_3.sock" } ] }, { "name": "api4", "mountPoints": [ { "containerPath": "/usr/src/tmp", "sourceVolume": "etc" } ], "environment": [ { "name": "SOCK_PATH", "value": "/usr/src/tmp/api_4.sock" } ] } ], "volumes": [{ "name": "etc" }], }
- 投稿日:2020-10-21T18:49:37+09:00
CentOS8にCaddyを入れずにdockerでCaddy起動
Caddyとは
Caddyのサイトに行くとデカデカと、そして堂々とこう書かれている。
THE ULTIMATE SERVER
なかなかここまでは断言できないものだ。
Caddyとは初版が2015年4月28日と新しいといえるオープンソースのウェブサーバーだ。 Go言語で記述されており、HTTP機能にはGo標準ライブラリを使用している。また他のウェブサーバーと異なり、HTTPSをデフォルトで使用する。CentOS8にCaddyを入れずにCaddy起動する
前回CentOS8にNginxを入れずにdockerを利用してNginxを起動したが、同じことをCaddyでやってみる。
Qiiaの記事はこっち。
環境
前回確認済みだが改めて環境を。
CentOS Linux release 8.2.2004 (Core)
Docker version 19.03.13, build 4484c46d9dCentOS8にCaddyを入れずにdockerでCaddy起動する
docker のコンテナもイメージも無い状態を確認する。
# docker ps -a
# docker images
Caddy起動する
# docker run --name testcaddy -d -p 8081:80 caddy
# docker ps -a
# docker images
特に何をすることもなく、caddyをPullしてコンテナの起動まで出来た。Firefoxでlocalhost:8081にアクセス
Firefoxでlocalhost:8081にアクセスする。8081なのはdocker runで「-p 8081:80」と指定した為だ。
問題なく、Caddyが動いているのが確認できた。
何に躓くことも無く、dockerでのCaddy起動は簡単に終わった。Caddy設定ファイルとindex.htmlの場所
Caddy起動まで何事も無かったので、Caddyのコンテナに少し触ってCaddy設定ファイルとindex.htmlの場所を確認してみる。
# docker exec -it testcaddy /bin/ash
# cd /etc/caddy
# ls
# cat Caddyfile
# Set this path to your site's directory.
root * /usr/share/caddy
と設定されている。
# cd /usr/share/caddy
# ls
# cat index.html
設定どおり/usr/share/caddyの下にindex.htmlがいることが確認できた。
Caddyをお試しするなら、dockerで行うのが楽で良さそうだ。
- 投稿日:2020-10-21T18:46:36+09:00
Dockerの基礎知識
Dockerとは
コンテナ仮想化を用いてアプリケーションを開発・配置・実行するためのオープンソースソフトウェアあるいはオープンプラットフォームの事である。
コンテナ仮想化を用いたOSレベルの仮想化によりアプリケーションを開発・実行環境から隔離し、アプリケーションの素早い提供を可能にします。
1つでもコンテナを作ってしまえば、そのコンテナをテスト環境、本番環境にもおける。さらにメンバー間でも同じコンテナを使うことによって開発環境の構築がかなり楽になる。
Docker imageからコンテナを作る。(下記図を参考)
Docker fileはDocker imageの設計図のようなもの。
Linuxについて
DockerはLinuxのコンテナ技術を利用している。つまりLinuxのOSの上に作っていく。
MacはunixベースのOS。Linuxはunixを元に作られる。
LinuxのOSにはKernelと呼ばれる核というものがある。
本来はKernelに命令を出したいが、私たちは直接命令を出せない。Shellを仲介してKernelに命令を出す。
Shellを使うときにはターミナルというbashやzshを動かすためのアプリを使用してShellがカーネルに命令を出してくれる。
shellの種類にはbash、zsh、sh..などがある。echo $SHELLコマンドを打つと自分の使っているshellがわかる。
環境変数
OSの上で動くプロセス。SHELLの環境変数はecho $SEHLLで中身を見れる。
環境変数を作る際は、exportというコマンドを使う。
//AGEという環境変数が作られる。 $export AGE=20 //環境変数を参照するとき $echo $AGELinuxの基礎コマンド
$cd <path> :<path>に移動する $pwd :今いるディレクトリの表示 $mkdir <new folder> :新しいフォルダの作成 $touch <new file> :新しいファイル<new file>を作成 $ls :カレントディレクトリのファイル、フォルダ一覧を表示 $rm <file>:<file>を削除 $rm -r <folder>:<folder>を削除DockerHub
Docker imageを管理するDockerレジストリ。
DockerHubにはDocker image(コンテナを作る元になるもの)が沢山登録されている。
それをHost(自分のPC)に持ってくる。そしてコンテナを作り、また、新しいDocker imageを作る。最後にそれをdockerhubにプッシュする。
dockerhubからhelloworldをpullする
#手順は主に3つ //dockerにログイン $docker login //pullして持ってくる $docker pull<image> //保存されているimageのリストを持ってくる $docker imagesTAGはdocker-imageのバージョン(デフォルトならlatest)
hello-worldのコンテナを作る
Docker imageはコンテナを作るためのもの。
それを今回作る。#手順は主に2つ //pullしてきたものをdocker runコマンドを使用してコンテナを作る。 $docker run<image> //今あるアクティブなコンテナの一覧を表示する $docker ps //全てのコンテナをみれる $docker ps -aUbuntuのDocker imageをrunする
ubuntuはLinuxっていうサーバーの派生のOS。
$docker run hello-world //-itはbash起動時に必要なおまじない(オプション) bashはコンテナ起動時に実行するプログラム $docker run -it ubuntu bash以下の画像だとubuntuというOSを起動してコンテナの中(root)に入って作業している。
再度コンテナを起動
$docker run -it ubuntu bash //execはコンテナを指定してコンテナに対してbashというプログラムを実行する。 $docker exec -it <container> bash$docker restart コンテナID でexitedをupに変えられる。exitとdetachの違い
ctrl+p+qで出る事ができる。
exitはコンテナを出るときにコンテナを動かしているプロセスを切って出る。
detachはコンテナを出るときにコンテナを動かしているプロセスは切らずに出る。//何かプロセスを残したいときはdetach(attach)を残す。 $docker attach コンテナID で元のプロセスに入る事ができる。 $exit をするとプロセスは消える。コンテナを新しくして他のメンバーにも反映させる方法
//コンテナの中でExitedになっているのをUpに変える。 $docker restart コンテナID //以下のコマンドはUpになってないとエラーになるので注意。コンテナの中に入る。 $docker exec -it コンテナID bash //lsコマンドで新しく更新すべきものがあるか確認 $ls //exitをしてコンテナから抜ける。しかし、$docker exec -it コンテナID bashで別のプロセスで入っているのでまだコンテナは動いている。 $exit //コンテナをdocker imageに保存する $docker commit コンテナID 新しいイメージ名(例 ubuntu:update(updateはタグ名になる))DockerHubに自分のリポジトリを作る
今までは、①ubuntuのDocker imageをDockerHubから持ってきて自分のhostに置く→②とってきたubuntuのDocker imageを使ってコンテナを作る→③そのコンテナの中にテストというファイルを置く→④そのコンテナをubuntu:updateという新しいimageで保存→⑤新しいDocker imageを他のメンバーに共有するためにdockerhubにpushする
※しかし、現状pushする場所がない。
なので自分用のリポジトリを作る必要があり、そこに新しいDocker imageをpushする。手順として
①DockerHubのページを開く→②create a Repositoryというボタンを押す→③リポジトリを作るページが開くので必要な情報を記入する→できたリポジトリに自分のimageをpushする(リポジトリとimage名は一致していないといけない)
新しく作ったimageを前回作ったリポジトリをプッシュする
dockerは1つのimageに対して1つのリポジトリが対応する。imageを作る際にプッシュ先を探す必要があり、imageをみて判断する。
//imageの名前を変える<source>(古いイメージ)→<target>(新しいイメージ) $docker tag <source> <target> //今回の場合はubuntu:updated→<username>/リポジトリ名。usernameを何も指定しないとlibraryになる。 $docker tag ubuntu:updated <username>/リポジトリ名 ※pushするためにリポジトリの名前を変えてもIMAGE IDは更新後も更新前も同じになるDockerHubにDocker imageをpushする
//pushするコマンド。これによりimageがpushされDockerHubのimage欄に記載ができる。 $docker push リポジトリ名(イメージ名)実際にpushされるのは一番上のみで残りの4つは既にあるもの。
pushする事で他のメンバーがイメージをpullしてコンテナを立てる事ができる。
自分で作ったリポをまたpullしてみる
同じimageがあるとうまくpullできないので一度自分のhostの中にあるimageを消す
//imageを削除する $docker rmi <image> //imageの確認 $docker images //docker pullコマンドでpullする。 $docker pull <image名> //コンテナを立てる $docker run -it <image> bashdocker runについて
$docker run はdocker imageからコンテナを立てて作るコマンドである。
runはcreate+start
startした後はデフォルトコマンドを実行した後、コンテナから抜けるのでExited状態になる。
ただコンテナを作るだけなら
//作るだけなのでSTATUSはcreatedになる。 $docker create <image> //以下のコマンドで作ったコンテナを動かす。 $docker start コンテナID //実行されたデフォルトコマンドを見るには以下のコマンドでテキスト出力の結果が見れる。 $docker start -a コンテナID※startはコンテナを起動させてデフォルトコマンド(bashではなく、ただのテキスト出力)を実行する。その後コンテナを抜ける。
今まではデフォルトコマンドにbashで上書きしていたためstatusがUpになっていた。
コマンドの上書きについて
//bashというコマンドで新たに上書きしていた。 $docker run -it <image> bash-itについて
i:インプット可能
t:表示が綺麗になるコンテナの削除
//コンテナの削除(containerはコンテナIDかイメージ名)。StatusがUp状態だと消せない。 $docker rm <container> //コンテナを止める(動いているコンテナを止める(Up→Exited)) $docker stop <container> //コンテナ全削除 $docker system pruneコンテナに名前をつける
名前をつけずにコンテナを立てるとDocker側が自動で名前をつける。
テスト環境のみなら名前をつけなくても良い場合はあるが、常に起動させておく場合(本番環境)なら名前をつけておく必要がある。//以下のようなコマンドで名前をつけてrunする。既に名前がある場合はエラーが出る。今回は-it bashをつけてないのでデフォルトのコマンドが実行される。 $docker run --name <name><image>detachedモードとforegroundモード
detachedモードはコンテナを起動時にdetachする(バックグラウンドで動かす)
$docker run -d <image>foregroundモードはコンテナをExit後に削除する(一回きりのコンテナ)
//テスト環境のみならforegroundを使う事が多い。 $docker run --rm <image>Dockerfileとは
今まではDockerHubから持ってきてDocker imageを作っていたがこれからはDockerfileから持ってきてDocker imageを作る。
どうゆうコンテナになるかはDockerfileで見る事ができるので業務ではDockerfileを使う事が多い。・Docker imageの設計図で、DockerfileからDocker imageを作る。
・Dockerfileというファイル名のテキスファイル。
・INSTRUCTION argumentsの形で書く。Dockerfileを作る
//テスト環境のみならforegroundを使う事が多い。 $cd~/フォルダ名 //ディレクトリ作成 $mkdir フォルダ名 //作成したフォルダに移動 $cd フォルダ名 //Dockerfile作成 $subl DockerfileDockerfileの書き方
//例 //FROMでベースとなるDocker imageを決めていく。今回はubuntuのlatestを指定。 FROM ubuntu:latest //RUNコマンドでubuntuのコンテナに対してtestファイルを作る。 RUN touch testDockerfileをbuildしてDocker imageを作る
//cd DockerfileがあるDockerフォルダに移動 //移動した先でbuildする(今回はDockerfileがあるカレントなので.を使う) $docker build . //以下のコマンドで確認するとREPOSITORY、TAGはnoneのまま。まだ何のimageもできていないため。 $docker images //以下のコマンドでnoneのみ出力できる $docker images -f dangling=true //名前をつけてbuildする場合(例)。REPOSITORYはnew-ubuntu、TAGはlatestになる。 $docker build -t new-ubuntu:latest .ビルドしたDocker imageをrunする
//(例) 以下のコマンドでnew-ubuntuのコンテナの中に入れる。実際にRUN touch testコマンドで実行したのでlsコマンドでtestファイルがあることを確認。 $docker run -it new-ubuntu bash root@xxxxxxx:/# lsDockerfileのinstructionの紹介
FROM
・ベースとなるイメージを決定する
・DockerfileはFROMから書き始めるDockerfile.//例 FROM ubuntu:latest //ベースとなるFROMを指定してからRUNコマンド等を実行する。 RUN touch testRUNについて
・Linuxコマンドを実行
・RUNを使うことで好きなようにカスタマイズ
・RUN毎にLayerが作られるDockerfile.//例 FROM ubuntu:latest //ベースとなるFROMを指定してからRUNコマンド等を実行する。 RUN touch test //testに'hello world'を入れる RUN echo 'hello world'>testしかし、RUNを増やすとimage layerが増えてDocker imageがすごく大きくなってしまう。
layer数を最小限にする方法
layerを作るのはRUN、COPY、ADDの3つ。
Ubuntuではapt-get(またはapt)というコマンドでパッケージ管理をする。
$apt-get update :新しいパッケージリストを取得 $apt-get install<package> :<package>をインストールDockerfile.//例(最小限にするために&&や\(バックスラッシュ)で改行する FROM ubuntu:latest //パッケージを使う RUN apt-get update && apt-get install\ xxx\ yyy\ zzzcacheを使おう
キャッシュを使うことで新しくbuildしないといけない手間を省く事ができる。
Dockerfile.FROM ubuntu:latest //パッケージを使うがインストールやアップデートは結構時間がかかる。そして新しいパッケージを増やすごとにbuildしないといけない手間がある。 RUN apt-get update && apt-get install -y \ xxx\ yyy\ zzz //以下を追加で入れたいとなったとき、キャッシュを使う事で最初のapt-getは使わずに以下の一行だけ実行できる RUN apt-get install\例えば以下のようにする。
Dockerfile.FROM ubuntu:latest //以下の1行だけでキャッシュを使ってくれることになる。 RUN apt-get update RUN apt-get install -y\ curl\ nginx\ //上記で一度curlとnginxはインストール済み。しかし、以下を追加すれば再度updateとinstallをしなくて済む RUN apt-get install cvscvsをインストールし終えたらまた、以下のように書き換える。
Dockerfile.FROM ubuntu:latest //以下の1行だけでキャッシュを使ってくれることになる。 RUN apt-get update RUN apt-get install -y\ curl\ cvs\ nginx作っている途中はRUNを細かく分けるが、作り終えたらまとめる。そうすればキャッシュを適用できる。
CMDについて
CMDはコマンドの略で、コンテナのデフォルトコマンドを指定する。
//以下の場合bin/bashを起動。""で囲んでいるものは実行するコマンドのこと。 CMD["/bin/bash"]RUNとCMDの違い
RUNはlayerを作る
CMDはDocker imageのデフォルトのコマンドを作る、layerは作らない。RUNして初めて実行される。docker buildは何をしているか
buildしたらDockerfileはDockerデーモンというものに渡す。Dockerデーモンはフォルダ(指定したフォルダをbuild context)とDockerfileを元に作っている。
Docker imageを作るときはDockerfileだけでなく、フォルダも必要。
そのフォルダのことはbuild contextという。コンテナやimageの操作はDocker deamonが行っていた。//ファイルのサイズを確認する時のコマンド $ du -sh ファイル名COPY
COPYを使うことでbuild context中のファイル(Dockerfile以外)をDocker imageに組み込んで最終的にはコンテナで使う事ができるようになる。
基本的にはコンテナのファイルシステムとHostのファイルシステムは別物。
Copyを使う事でファイルの受け渡しをHostからコンテナにできる。ADD
単純にファイルやフォルダをコピーする場合は、COPYを使う。
ADDはtar(アーカイブファイル(複数のファイルやフォルダを1つにまとめたファイル)の作り方(ファイルのまとめ方)のひとつ)の圧縮ファイルをコピーして解凍したい時に使う。また、Docker imageを作る時に解凍もしてくれる。仮にDockerfile以外のファイルがサイズが大きいものだったりした場合、ファイルを圧縮して持ってきたいケースが結構ある。それを解決する。
Dockerfileがbuild contextにない場合
以下のようなコマンドでビルトコンテキストになくてもbuildできる。
buildcontextの中にDockerfileはない。親ディレクトリのDockerfile.devにある。 $docker build -f Dockerfile.dev .CMDとENTRYPOINTの違い
ENV
環境変数(OSの上で動くあらゆるプロセスや情報を共有するために使う変数)を設定してくれる。
//ENVの書き方 ①1つだけ ENV <key> <value> ②複数の場合 ENV <key>=<value>...WORKDIR
DockerfileにDocker instructionの記述がある。そのDocker instructionを実行するディレクトリを変更してくれる。
上記のDockerfileの記載だとどこにsample_folderが作られるか不明。WORKDIRを使うと指定できる。以下のように記載
cdするために&&を使うのではなく、WORKDIRを使うと指定したディレクトリで実行できる。ホストとコンテナの関係について詳しく
今までコンテナのファイルシステムは別のコンテナのファイルシステムやHostのファイルシステムとは全く別のものを持っていたが、コンテナからHostのファイルシステムにアクセスできるようになる。
しかし、誰でもアクセスできてはいけないのでアクセス権限の設定が必要。-vオプションを使ってファイルシステムを共有する
通常Hostとコンテナのファイルシステムは全く別物でコンテナからHostのファイルシステムには全くアクセスできない状態だった。
あたかもHostのファイルシステムがコンテナのファイルシステムにあるかのように振る舞う事ができる。
実際にフォルダがコンテナの中にあるわけではないが、マウントすることによってHostのファイルが見える。Hostにcodeを置いといてコンテナにはおかないのは、コンテナが無茶苦茶大きくなるから。codeを実行するときにコンテナを使う。
-vオプションの時、フォルダがコンテナにない場合は自動で作ってくれる。-uオプションを使ってホストとコンテナのアクセス権限を共有する
指定したユーザーIDとグループIDを指定してコンテナをrunする
-pオプションを使って、ホストとコンテナのポートをつなげる
ポートとはプロセスがデータ通信するために使うもの。
HostのIPアドレスだけだとどのwebサービスにアクセスしたらわからない時にポートを使う。
ポートは部屋番号みたいなもの。-pはポートのP。publishのこと。
コンテナで使えるコンピュータリソースの上限を設定する
コンテナはHostの上で立っているのでHostのメモリやcpuを使って動かしている。
- 投稿日:2020-10-21T12:41:30+09:00
Djangoチュートリアル(ブログアプリ作成)⑥ - 記事詳細・編集・削除機能編
前回、Djangoチュートリアル(ブログアプリ作成)⑤ - 記事作成機能編では
アプリ内で記事を作成する機能を追加しました。今回は CRUD でいうところの C(Create) が出来るようになったので、残りの機能 (詳細、編集、削除) 機能を追加していきます。
記事の詳細表示、編集、削除機能もクラスベース汎用ビューで実装していきます。
urls.py の修正
これから盛り込む機能では、各記事の固有となるキーをもとにアクセスをして編集することになります。
そのキーとなる値が各記事のプライマリキーです。Django のクラスベース汎用ビューの機能を使うと、urls.py 内でのルーティング設定において
各記事のプライマリキーint:pk という文字列で URL パターンを指定することができます。例えば詳細表示機能でいうと 'blog/post_detail/int:pk' というURLパターンを設定するだけで
作られた記事をプライマリキーで判別し、詳細・編集・削除を司る View へのルーティングが可能になります。view クラスの名前はそれぞれ下記の名前で設定していきます。
- 詳細:PostDetailView
- 編集:PostUpdateView
- 削除:PostDeleteView
この時、urls.py は次のようになります。
urls.pyfrom django.urls import path from . import views app_name = 'blog' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('post_list', views.PostListView.as_view(), name='post_list'), path('post_create', views.PostCreateView.as_view(), name='post_create'), path('post_detail/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'), # 追加 (例) /blog/detail/1 ※特定のレコードに対して処理を行うので pk で識別 path('post_update/<int:pk>/', views.PostUpdateView.as_view(), name='post_update'), # 追加 (例) /blog/update/1 ※同上 path('post_delete/<int:pk>/', views.PostDeleteView.as_view(), name='post_delete'), # 追加 (例) /blog/delete/1 ※同上 ]各記事の detail, update, delete についてはスラッシュ区切りのプライマリキーで指定していることが分かるかと思います。
これで、例えば一番最初の記事の詳細であれば /blog/post_detail/1/ でアクセスできるようになります。
(ローカルホストであれば 127.0.0.1:8000/blog/post_detail/1 という URL でブラウザからアクセスできます)views.py の修正
続けて、各ビューを作成していきます。
先に完成形をお見せします。views.py... class PostDetailView(generic.DetailView): # 追加 model = Post # pk(primary key)はurls.pyで指定しているのでここではmodelを呼び出すだけで済む class PostUpdateView(generic.UpdateView): # 追加 model = Post form_class = PostCreateForm # PostCreateFormをほぼそのまま活用できる success_url = reverse_lazy('blog:post_detail') class PostDeleteView(generic.DeleteView): # 追加 model = Post success_url = reverse_lazy('blog:post_list')PostDetailView の解説
コード量の少なさに驚いたのではないでしょうか?
特にプライマリキーのことなどは記述する必要がなく、汎用ビューの力のおかげでたったこれだけで詳細ページを作成できるようになります。
また、template を post_detail.html というファイル名にしてあげることで自動的に識別してくれるようになります。PostUpdateView
実は UpdateView は、新規作成時と同じフォームをそのまま使えるという特徴もあり CreateView と酷似しています。
python:views.py の解説
...
class PostCreateView(generic.CreateView):
model = Post
form_class = PostCreateForm
success_url = reverse_lazy('blog:post_list')
...
違うのは、編集成功時の success_url を UpdateView では記事詳細画面としているぐらいですが、
これも記事一覧 (post_list) と同じでよければ全く中身は同じコートでいけてしまいます。また、実は PostCreate で使っている post_form.html をそのまま流用することができるため、
template を新たに用意する必要はありません。PostDetailView の解説
削除では、入力フォームを用いるわけではないのでフォームの呼び出しは必要ありません。
ただし、Update とは違う点で2つ注意が必要になります。ひとつは、template 名は post_confirm_delete.html とすることです。
これまでの流れから post_delete.html としてしまいそうになりますが、
アクセスしてもいきなり削除するわけではないことから少し名前の定義が異なります。もう一つは、削除成功時のリダイレクト先です。
UpdateView では一覧画面か詳細画面、どちらでもよかったのですが
削除をしてしまうとアクセス対象の記事がなくなってしまうので、詳細画面 (post_detail) にはリダイレクトさせないように注意してください。template の準備
では views.py でも少し触れた通り、template/blog/ 配下に詳細、削除用の template (html) を作ります。
└── templates └── blog ├── index.html ├── post_confirm_delete.html # 追加 ├── post_detail.html # 追加 ├── post_form.html └── post_list.htmlでは、まずは post_detail.html を編集していきます。
post_detail.html<table> <tr> <th>タイトル</th> <td>{{ post.title }}</td> </tr> <tr> <th>本文</th> <!-- linebreaksbk を入れると改行タグでちゃんと改行して表示されるようになる --> <td>{{ post.text | linebreaksbr}}</td> </tr> <tr> <th>日付</th> <td>{{ post.date }}</td> </tr> </table>post という変数を template 側で受け取って {{ post.title }} & {{ post.text }} & {{ post.date }} で表示させています。
なお post.text 後にある 「| linebreaksbr」 を入れると、記事本文のように長いものでも自動で改行してくれるようになります。次に post_confirm_delete.html を編集します。
post_confirm_delete.html<form action="" method="POST"> <table> <tr> <th>タイトル</th> <td>{{ post.title }}</td> </tr> <tr> <th>本文</th> <td>{{ post.text }}</td> </tr> <tr> <th>日付</th> <td>{{ post.date }}</td> </tr> </table> <p>こちらのデータを削除します。</p> <button type="submit">送信</button> {% csrf_token %} </form>最初の行では、記事の削除という POST メソッドを使うため、form では POST メソッドを指定しています。
POST 送信先など、処理は view 側で調整をしてくれるので template 側では記述する必要はありません。
最後に CSRF 対策で {% csrf_token %} を記述することにも注意しましょう。これで template の編集は終了です。
ユニットテストの作成
先に追加
test_views.py... class PostDetailTests(TestCase): # 追加 """PostDetailView のテストクラス""" def test_not_fount_pk_get(self): """記事を登録せず、空の状態で存在しない記事のプライマリキーでアクセスした時に 404 が返されることを確認""" response = self.client.get( reverse('blog:post_detail', kwargs={'pk': 1}), ) self.assertEqual(response.status_code, 404) def test_get(self): """GET メソッドでアクセスしてステータスコード200を返されることを確認""" post = Post.objects.create(title='test_title', text='test_text') response = self.client.get( reverse('blog:post_detail', kwargs={'pk': post.pk}), ) self.assertEqual(response.status_code, 200) self.assertContains(response, post.title) self.assertContains(response, post.text) class PostUpdateTests(TestCase): # 追加 """PostUpdateView のテストクラス""" def test_not_fount_pk_get(self): """記事を登録せず、空の状態で存在しない記事のプライマリキーでアクセスした時に 404 が返されることを確認""" response = self.client.get( reverse('blog:post_update', kwargs={'pk': 1}), ) self.assertEqual(response.status_code, 404) def test_get(self): """GET メソッドでアクセスしてステータスコード200を返されることを確認""" post = Post.objects.create(title='test_title', text='test_text') response = self.client.get( reverse('blog:post_update', kwargs={'pk': post.pk}), ) self.assertEqual(response.status_code, 200) self.assertContains(response, post.title) self.assertContains(response, post.text) class PostDeleteTests(TestCase): # 追加 """PostDeleteView のテストクラス""" def test_not_fount_pk_get(self): """記事を登録せず、空の状態で存在しない記事のプライマリキーでアクセスした時に 404 が返されることを確認""" response = self.client.get( reverse('blog:post_delete', kwargs={'pk': 1}), ) self.assertEqual(response.status_code, 404) def test_get(self): """GET メソッドでアクセスしてステータスコード200を返されることを確認""" post = Post.objects.create(title='test_title', text='test_text') response = self.client.get( reverse('blog:post_delete', kwargs={'pk': post.pk}), ) self.assertEqual(response.status_code, 200) self.assertContains(response, post.title) self.assertContains(response, post.text)GET メソッドで 200 が返ってくることを確認するあたりはこれまでのユニットテストと大きく変わりはありませんが、
存在はずの記事に reponse を得るあたりの処理が大きく違っています。... def test_not_fount_pk_get(self): """記事を登録せず、空の状態で存在しない記事のプライマリキーでアクセスした時に 404 が返されることを確認""" response = self.client.get( reverse('blog:post_delete', kwargs={'pk': 1}), ) self.assertEqual(response.status_code, 404) ...ユニットテストの中でデータベースにデータを登録(ここでいえば記事を作成)できることは既にお伝えしました。
しかし、記事が存在していない状態でプライマリキーが 1 の記事=最初の記事の詳細・編集・削除画面へアクセスしようとすると
当然ページは存在していないので、ページが存在していないことを示す HTTPステータスコードの 404 を返してきます。上記のテストメソッドでは、あえて存在しない記事の個別ページへアクセスして操作をしようとしても
エラーが返ってくることを確認しています。これでユニットテストを実行すると、エラーもなく通るはずです。
(blog) bash-3.2$ python3 manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ................. ---------------------------------------------------------------------- Ran 17 tests in 0.460s OK Destroying test database for alias 'default'...これで CRUD 機能を無事に追加することができたため、大きな機能としてはおしまいです。
次は template を修正し、全画面に共通のナビゲーションバーをつけることで全ページで表示させていきましょう。
- 投稿日:2020-10-21T01:02:20+09:00
CircleCIでEC2デプロイ時にエラー
はじめに
個人アプリにてCircleCIを導入し、
EC2にデプロイしようとした所、いくつかエラーが発生したので、解決策を記載します。環境
- ruby 2.6.2
- rails 5.2.4
- EC2
config.yml
version: 2 jobs: # build ジョブ: CircleCI 上で Docker コンテナを作成してテストする build: docker: - image: alpine steps: - checkout - run: name: Echo Test command: echo "CircleCI Test" # deploy ジョブ: EC2 に SSH 接続して、デプロイを実行する deploy: machine: image: circleci/classic:edge steps: - checkout # CircleCI に登録した秘密鍵を呼び出す - add_ssh_keys: # CircleCI に登録した環境変数を使って SSH - run: ssh ${USER_NAME}@${HOST_NAME} 'cd /var/www/myapp && git pull origin master' workflows: version: 2 # build_and_deploy ジョブ: 一番最初に呼ばれるジョブ build_and_deploy: # build ジョブと deploy ジョブを呼び出す jobs: - build - deploy: requires: # deploy ジョブより先に build ジョブを実行しろ! - build # master ブランチに push された場合のみ deploy ジョブを実行する filters: branches: only: master起こった問題と解決策
①ポートが開いてないことによるエラー
ssh: connect to host ************* port 22: Connection timed out【解決策】
EC2のセキリティグループのインバウンドを下記のように編集する。
注)セキリティ上、中間サーバーを使用するほうが好ましい
ポート範囲 ソース 22 マイIP xxx.xxx〜 ↓
ポート範囲 ソース 22 カスタム 0.0.0.0/0 ②秘密鍵が合わないことによるエラー
①EC2にて下記のコマンドを実行し、pem鍵を作成(他の鍵と競合する可能性がある為、circleci-keyなどの名前にする)
ssh-keygen -m pem②EC2にて下記のコマンドを実行し、①で作成した公開鍵(鍵名.pub)をauthorized_keysにコピー
cat ~/.ssh/circleci-key.pub >> authorized_keys③EC2にて下記のコマンドを実行し、①で作成した秘密鍵を表示
cat ~/.ssh/circleci-key④表示された秘密鍵をCircleCIのSSH-Keyに設定
(このとき-----BEGIN RSA PRIVATE KEY-----と-----END RSA PRIVATE KEY-----まで含める)最後に
私の場合、これでエラーが解消されましたので、
是非ためしてみてください。参考
- 投稿日:2020-10-21T00:39:35+09:00
[AWS][AmazonLinux]docker-composeでディスク容量不足のエラー分析
背景
コンテナを起動しようとしたところ怒られた
# docker-compose up ERROR: failed to register layer: Error processing tar file(exit status 1): write /usr/sbin/mysqld: no space left on device
どうやらデバイスに容量がないらしい
原因分析
全ファイルシステムの空き容量を見る
dfコマンド・・・パーティションサイズ、空き容量をパーティションごとに表示する# df -h Filesystem 1K-blocks Used Available Use% Mounted on devtmpfs 485220 0 485220 0% /dev tmpfs 503468 0 503468 0% /dev/shm tmpfs 503468 704 502764 1% /run tmpfs 503468 0 503468 0% /sys/fs/cgroup /dev/xvda1 8376300 7869788 506512 94% / tmpfs 100696 0 100696 0% /run/user/1000
おそらく/dev/xvda1が94%を使用していて、dockerイメージをDLできないらしい。
では何に容量を食っているのか。調べたところ、もともと入っているライブラリが容量を食っているらしい。
Amazon無料枠に余裕はない模様
duコマンド・・・ディスクの使用量を表示するコマンド# du -sh /usr/* 817M /usr/bin 0 /usr/etc 0 /usr/games 28M /usr/include 344M /usr/lib 613M /usr/lib64 90M /usr/libexec 12M /usr/local 1.9G /usr/sbin 355M /usr/share 0 /usr/src 0 /usr/tmp
解決
最低限使えればいいのでディスク容量を8GB→12GB拡張する。(AWSを利用している方はおそらく課金対象になるため注意)
Amazon Linxのディスク容量拡張方法については下記参照
https://qiita.com/Ponkotwo/items/fd87d356fd3a735fde99