20201021のdockerに関する記事は8件です。

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.yml
jobs:
  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_USERNAMEDOCKERHUB_TOKENは、あらかじめsecretsとしてGitHubに登録しておきましょう(公式Doc

注意点として、DOCKERHUB_TOKEN にはパスワードではなくAccess Tokenを使うことが推奨されています。パスワードでも動きますが、ちゃんとAccess Tokenを発行しましょう。

Access Tokenの発行方法

  1. https://hub.docker.com/ にログイン
  2. 右上のユーザー名から Account Settings
  3. Security -> New Access Token -> あとは流れに乗る スクリーンショット 2020-10-21 20.28.35.png

発行されたAccess TokenをDOCKERHUB_TOKENとして登録すれば完了。docker/login-actionでログインできるようになります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでSparkを動かしたらlocalhost:4040にアクセスできない件について

SparkのSpark Web UI (localhost:4040)だけが何故か接続できなかったので、色々調べていたのですが解決策が乗っておらず、慣れていない人であれば同じ悩みを抱えている人がいると思ったので、初投稿します。また業務中に起きたエラーのため、コードなどは一切載せていませんのでご理解のほどよろしくお願いいたします。

環境

DockerでPythonのコンテナとSparkのコンテナ(wokerを接続している)をBridgeしている
どちらの環境にもPysparkjavaの環境がインストールされている。

環境構築の参考

SparkのMaster1つとWorker1つをdocker-composeで上げてpysparkを動かすまで
https://qiita.com/hrkt/items/fe9b1162f7a08a07e812

ゴール

PythonのDockerの中に入って(docker exec...) spark-submitを行い、処理を行っている間にlocalhost:4040に接続してWebUIを確認したい。

問題について

起動してからlocalhost:8080はつながるのになぜか4040は接続ができない

解決策

  1. Sparkが処理中時にのみlocalhost:4040にアクセスできる
  2. 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')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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/tmp

sockのパスを環境変数で指定させる

main.js
const 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.conf
upstream 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" }],
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CentOS8にCaddyを入れずにdockerでCaddy起動

2020-10-21_170336.png

Caddyとは

Caddyのサイトに行くとデカデカと、そして堂々とこう書かれている。
THE ULTIMATE SERVER
なかなかここまでは断言できないものだ。
Caddyとは初版が2015年4月28日と新しいといえるオープンソースのウェブサーバーだ。 Go言語で記述されており、HTTP機能にはGo標準ライブラリを使用している。また他のウェブサーバーと異なり、HTTPSをデフォルトで使用する。

CentOS8にCaddyを入れずにCaddy起動する

前回CentOS8にNginxを入れずにdockerを利用してNginxを起動したが、同じことをCaddyでやってみる。
Qiiaの記事はこっち。
2020-10-21_182034.png

環境

前回確認済みだが改めて環境を。
CentOS Linux release 8.2.2004 (Core)
Docker version 19.03.13, build 4484c46d9d

CentOS8にCaddyを入れずにdockerでCaddy起動する

docker のコンテナもイメージも無い状態を確認する。
# docker ps -a
# docker images
2020-10-21_180219.png
Caddy起動する
# docker run --name testcaddy -d -p 8081:80 caddy
# docker ps -a
# docker images
2020-10-21_180252.png
特に何をすることもなく、caddyをPullしてコンテナの起動まで出来た。

Firefoxでlocalhost:8081にアクセス

Firefoxでlocalhost:8081にアクセスする。8081なのはdocker runで「-p 8081:80」と指定した為だ。
2020-10-21_172725.png
問題なく、Caddyが動いているのが確認できた。
何に躓くことも無く、dockerでのCaddy起動は簡単に終わった。

Caddy設定ファイルとindex.htmlの場所

Caddy起動まで何事も無かったので、Caddyのコンテナに少し触ってCaddy設定ファイルとindex.htmlの場所を確認してみる。
# docker exec -it testcaddy /bin/ash
# cd /etc/caddy
# ls
# cat Caddyfile
2020-10-21_180526.png
# Set this path to your site's directory.
root * /usr/share/caddy
と設定されている。
# cd /usr/share/caddy
# ls
# cat index.html
2020-10-21_180618.png
設定どおり/usr/share/caddyの下にindex.htmlがいることが確認できた。
Caddyをお試しするなら、dockerで行うのが楽で良さそうだ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerの基礎知識

Dockerとは

コンテナ仮想化を用いてアプリケーションを開発・配置・実行するためのオープンソースソフトウェアあるいはオープンプラットフォームの事である。

コンテナ仮想化を用いたOSレベルの仮想化によりアプリケーションを開発・実行環境から隔離し、アプリケーションの素早い提供を可能にします。

1つでもコンテナを作ってしまえば、そのコンテナをテスト環境、本番環境にもおける。さらにメンバー間でも同じコンテナを使うことによって開発環境の構築がかなり楽になる。

スクリーンショット 2020-10-19 6.45.05.png

Docker imageからコンテナを作る。(下記図を参考)
Docker fileはDocker imageの設計図のようなもの。
スクリーンショット 2020-10-19 6.47.13.png

Linuxについて

DockerはLinuxのコンテナ技術を利用している。つまりLinuxのOSの上に作っていく。
MacはunixベースのOS。Linuxはunixを元に作られる。

スクリーンショット 2020-10-19 6.59.50.png
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 $AGE

Linuxの基礎コマンド

$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にプッシュする。
スクリーンショット 2020-10-19 7.20.02.png

dockerhubからhelloworldをpullする

#手順は主に3つ

//dockerにログイン
$docker login
//pullして持ってくる
$docker pull<image>
//保存されているimageのリストを持ってくる
$docker images

以下のようなコマンドのイメージ
スクリーンショット 2020-10-19 22.22.28.png

TAGはdocker-imageのバージョン(デフォルトならlatest)

hello-worldのコンテナを作る

Docker imageはコンテナを作るためのもの。
それを今回作る。

#手順は主に2つ
//pullしてきたものをdocker runコマンドを使用してコンテナを作る。
$docker run<image>
//今あるアクティブなコンテナの一覧を表示する
$docker ps
//全てのコンテナをみれる
$docker ps -a

スクリーンショット 2020-10-19 23.10.05.png

UbuntuのDocker imageをrunする

ubuntuはLinuxっていうサーバーの派生のOS。

$docker run hello-world

//-itはbash起動時に必要なおまじない(オプション) bashはコンテナ起動時に実行するプログラム
$docker run -it ubuntu bash

以下の画像だとubuntuというOSを起動してコンテナの中(root)に入って作業している。
スクリーンショット 2020-10-20 10.23.42.png
スクリーンショット 2020-10-20 10.26.16.png

スクリーンショット 2020-10-20 10.26.40.png

再度コンテナを起動

$docker run -it ubuntu bash
//execはコンテナを指定してコンテナに対してbashというプログラムを実行する。
$docker exec -it <container> bash
$docker restart コンテナID でexitedをupに変えられる。

スクリーンショット 2020-10-20 10.55.13.png

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する
スクリーンショット 2020-10-20 21.06.18.png

※しかし、現状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 リポジトリ名(イメージ名) 

スクリーンショット 2020-10-20 23.07.20.png

実際に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> bash

docker runについて

$docker run はdocker imageからコンテナを立てて作るコマンドである。

runはcreate+start
startした後はデフォルトコマンドを実行した後、コンテナから抜けるのでExited状態になる。
スクリーンショット 2020-10-21 19.07.03.png

ただコンテナを作るだけなら

//作るだけなので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 Dockerfile

Dockerfileの書き方

//例

//FROMでベースとなるDocker imageを決めていく。今回はubuntuのlatestを指定。
FROM ubuntu:latest
//RUNコマンドでubuntuのコンテナに対してtestファイルを作る。
RUN touch test

Dockerfileを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:/# ls

Dockerfileのinstructionの紹介

FROM

・ベースとなるイメージを決定する
・DockerfileはFROMから書き始める

Dockerfile.
//例
FROM ubuntu:latest
//ベースとなるFROMを指定してからRUNコマンド等を実行する。
RUN touch test

RUNについて

・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\ 
zzz

cacheを使おう

キャッシュを使うことで新しく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 cvs

cvsをインストールし終えたらまた、以下のように書き換える。

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は何をしているか

スクリーンショット 2020-10-22 22.11.49.png

buildしたらDockerfileはDockerデーモンというものに渡す。Dockerデーモンはフォルダ(指定したフォルダをbuild context)とDockerfileを元に作っている。
Docker imageを作るときはDockerfileだけでなく、フォルダも必要。
そのフォルダのことはbuild contextという。コンテナやimageの操作はDocker deamonが行っていた。

スクリーンショット 2020-10-23 0.03.39.png

//ファイルのサイズを確認する時のコマンド
$ du -sh ファイル名

COPY

COPYを使うことでbuild context中のファイル(Dockerfile以外)をDocker imageに組み込んで最終的にはコンテナで使う事ができるようになる。
基本的にはコンテナのファイルシステムとHostのファイルシステムは別物。
Copyを使う事でファイルの受け渡しをHostからコンテナにできる。

ADD

単純にファイルやフォルダをコピーする場合は、COPYを使う。
ADDはtar(アーカイブファイル(複数のファイルやフォルダを1つにまとめたファイル)の作り方(ファイルのまとめ方)のひとつ)の圧縮ファイルをコピーして解凍したい時に使う。また、Docker imageを作る時に解凍もしてくれる。

仮にDockerfile以外のファイルがサイズが大きいものだったりした場合、ファイルを圧縮して持ってきたいケースが結構ある。それを解決する。

スクリーンショット 2020-10-23 0.27.14.png

Dockerfileがbuild contextにない場合

スクリーンショット 2020-10-23 0.42.08.png

以下のようなコマンドでビルトコンテキストになくてもbuildできる。

buildcontextの中にDockerfileはない。親ディレクトリのDockerfile.devにある。
$docker build -f Dockerfile.dev .

CMDとENTRYPOINTの違い

スクリーンショット 2020-10-23 0.50.34.png

ENV

環境変数(OSの上で動くあらゆるプロセスや情報を共有するために使う変数)を設定してくれる。

//ENVの書き方
①1つだけ
ENV <key> <value>
②複数の場合
ENV <key>=<value>... 

スクリーンショット 2020-10-27 20.23.12.png

WORKDIR

DockerfileにDocker instructionの記述がある。そのDocker instructionを実行するディレクトリを変更してくれる。

スクリーンショット 2020-10-27 20.40.53.png

上記のDockerfileの記載だとどこにsample_folderが作られるか不明。WORKDIRを使うと指定できる。以下のように記載
スクリーンショット 2020-10-27 20.44.31.png
cdするために&&を使うのではなく、WORKDIRを使うと指定したディレクトリで実行できる。

ホストとコンテナの関係について詳しく

スクリーンショット 2020-10-27 23.54.57.png

今までコンテナのファイルシステムは別のコンテナのファイルシステムやHostのファイルシステムとは全く別のものを持っていたが、コンテナからHostのファイルシステムにアクセスできるようになる。
しかし、誰でもアクセスできてはいけないのでアクセス権限の設定が必要。

-vオプションを使ってファイルシステムを共有する

通常Hostとコンテナのファイルシステムは全く別物でコンテナからHostのファイルシステムには全くアクセスできない状態だった。

スクリーンショット 2020-10-30 10.45.41.png

スクリーンショット 2020-10-28 0.08.10.png

あたかもHostのファイルシステムがコンテナのファイルシステムにあるかのように振る舞う事ができる。
実際にフォルダがコンテナの中にあるわけではないが、マウントすることによってHostのファイルが見える。

Hostにcodeを置いといてコンテナにはおかないのは、コンテナが無茶苦茶大きくなるから。codeを実行するときにコンテナを使う。

スクリーンショット 2020-10-28 0.21.30.png
-vオプションの時、フォルダがコンテナにない場合は自動で作ってくれる。

-uオプションを使ってホストとコンテナのアクセス権限を共有する

スクリーンショット 2020-10-28 0.32.38.png

指定したユーザーIDとグループIDを指定してコンテナをrunする

スクリーンショット 2020-10-28 0.48.33.png

パーミッションの見方
スクリーンショット 2020-10-28 0.52.08.png
スクリーンショット 2020-10-28 0.55.32.png

-pオプションを使って、ホストとコンテナのポートをつなげる

ポートとはプロセスがデータ通信するために使うもの。
HostのIPアドレスだけだとどのwebサービスにアクセスしたらわからない時にポートを使う。
ポートは部屋番号みたいなもの。

スクリーンショット 2020-10-30 11.23.42.png

-pはポートのP。publishのこと。

コンテナで使えるコンピュータリソースの上限を設定する

スクリーンショット 2020-10-30 11.35.08.png

コンテナはHostの上で立っているのでHostのメモリやcpuを使って動かしている。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.py
from 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 を修正し、全画面に共通のナビゲーションバーをつけることで全ページで表示させていきましょう。

→次回
Djangoチュートリアル(ブログアプリ作成)⑦ - フロントエンド完成編

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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-----まで含める)

 最後に

私の場合、これでエラーが解消されましたので、
是非ためしてみてください。

 参考

CircleCIの使い方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む