terraformを使えるようにする設定
IAMでterraformからインフラを作成するためのユーザーを作成する。 作成するとアクセスキーとシークレットキーが手に入るので、これを環境変数に加えておくこと。 さらにはデフォルトのリージョンも環境変数として定める。
vi ~/.bash_profile
#追加 #Terraform export AWS_ACCESS_KEY_ID=AKIAY3ADHOAGPAG5KKEUK export AWS_SECRET_ACCESS_KEY=J/o+Ijtgtg1UystestCAvfvng7Alb export AWS_DEFAULT_REGION=ap-northeast-1
#環境変数を反映 source ~/.bash_profile
#以下のコマンドで数字の列が返されたらterraformの使う準備としては第一段階クリア aws sts getcalleridentity query Account output text
brew install terraform
使い方
terraform init
terraform plan
terraform apply
基本の基本 EC2にssh接続する(復習)
コンテンツ
- マネージドコンソールを使ってEC2を立ち上げ、SSH接続を行う
- TERRAFORMから同じことを実現させる
一番最初の基本形としてEC2インスタンスを立ち上げて、ssh接続するまでをマネージドコンソールでどのようにセッティングすればよいかメモしておく。
手順
- VPC領域を決める
- VPC領域内に存在させるサブネットを作成する
- インターネットゲートウェイを作成し、VPCにアタッチする
- ルートテーブルを作成し、サブネットにアタッチする。ルートテーブルには作成したインターネットゲートウェイをアタッチする。
- セキュリティグループを作成し、sshのポートを開けておく。
- 最後にec2のインスタンスを作成する。この際に上記で作成したものがきちんと使用されているか注意する。またパブリックIPの確保も気をつける。
相関関係
要素 | 割り当てる、アタッチ対象 |
---|---|
サブネット | VPC |
インターネットゲートウェイ | VPC |
ルートテーブル | サブネット |
セキュリティグループ | EC2インスタンス |
要素 | 何を設定スべき |
---|---|
VPC | ネットワークの構成 |
サブネット | サブネットの領域 |
ルートテーブル | インターネットに接続したい場合はこちらにインターネットゲートウェイの情報を登録する |
セキュリティグループ | IPとポートを制限しているが、sshやhttp,DBの接続用にポートを開けること考えなければならない。 |
参考資料
【AWS EC2 エラー】ssh port 22 Operation timed out - Qiita
AWS EC2でパブリックDNS/IPが割り当てられない - huamutouの日記
pingで通信ができるようにする(補足)
さくらVPSのcentosでは特にicmpプロトコル(PINGが使うプロトコル)に関わるファイアウォールを設定しなくてもpingを通すことができた。 しかしec2の場合はicmpのポートを開けなければ、pingが使えない。セキュリティグループのインバウンドにおいてicmpを開けておく設定をしておくこと。
本題terraformで実行するとどうなるのか??
ちょうどやりたいことが以下の説明にあった。
AWSをテラフォーミングする会(Terraformハンズオン)
*注意点 上記の資料は結構昔のものなんだけれども、terraformは下方互換性がない。したがって自分のバージョンは0.12.5なんだけれども、このバージョンでは動かなかった。
修正点を以下に記す。
tags {} ではなくtags = {}と記述する
他にも良い記事があった。
SSHでアクセスできるEC2インスタンスを構築する - togatttiのエンジニアメモ
TerraformでVPC・EC2インスタンスを構築してssh接続する - Qiita
terraformで実現するための留意点
マネージドコンソールを使ってEC2を立ち上げる場合を考えてみると、EC2インスタンスを生成する時に.pemダウンロードすることになる。正確に言えば、新しい.pemをダウンロードするか、既存の.pemをec2インスタンスに登録する。そして、この.pemを使ってssh接続することになる。
そしてterraformを使ってec2を立ち上げるときのことを考えるとどうだろうか。 ec2を立ち上げるときに.pemをブラウザを通してダウンロードする事ができるだろうか。まずCUIで操作するのでそんな事は考えられない。したがってマネージドコンソールで自動的に行われていた.pemに関する作業は、自分で行わなければならないことがわかるだろう。
したがってマネージドコンソールで色んな要素を作成してEC2を組み立てること + ssh接続に関する設定を加えると想像することは明らかだ。以下の作業ではssh接続するためのkeyを生成すること、ec2に当該公開鍵を登録する操作ec2インスタンスを立ち上げる作業に加えている。
手順(全体)
terraformを使えるようにセットアップする
特に説明事項はない。brewでインストールして使う。
ディレクトリを準備する
$ mkdir example
sshで接続するためのkeyを生成する
$ cd example $ ssh-keygen -t rsa -f id_rsa_ec2
ファイルを作成する
$ mkdir ec2_ssh $ cd ec2_ssh
$ touch ec2.tf $ vi ec2.tf
# ec2_ssh/ec2.tfの内容 # EC2 Instance resource "aws_instance" "example" { ami = "ami-0873b46c45c11058d" vpc_security_group_ids = [aws_security_group.example.id] subnet_id = aws_subnet.example.id key_name = "id_rsa_ec2" #EC2にKeyPairを登録 instance_type = "t2.micro" tags = { Name = "example" } } # Key Pair resource "aws_key_pair" "example" { key_name = "id_rsa_ec2" public_key = file("../id_rsa_ec2.pub") # 先程`ssh-keygen`コマンドで作成した公開鍵を指定 }
touch vpc.tf
# ec2_ssh/vpc.tfの内容 # VPC resource "aws_vpc" "example" { cidr_block = "10.0.0.0/16" enable_dns_support = true # DNS解決を有効化 enable_dns_hostnames = true # DNSホスト名を有効化 tags = { Name = "example" } } # Subnet resource "aws_subnet" "example" { cidr_block = "10.0.1.0/24" availability_zone = "us-west-2a" vpc_id = aws_vpc.example.id # trueにするとインスタンスにパブリックIPアドレスを自動的に割り当ててくれる map_public_ip_on_launch = true tags = { Name = "example" } } # Internet Gateway resource "aws_internet_gateway" "example" { vpc_id = aws_vpc.example.id tags = { Name = "example" } } # Route Table resource "aws_route_table" "example" { vpc_id = aws_vpc.example.id tags = { Name = "example" } } resource "aws_route" "example" { gateway_id = aws_internet_gateway.example.id route_table_id = aws_route_table.example.id destination_cidr_block = "0.0.0.0/0" } resource "aws_route_table_association" "example" { subnet_id = aws_subnet.example.id route_table_id = aws_route_table.example.id } # Security Group resource "aws_security_group" "example" { vpc_id = aws_vpc.example.id name = "example" tags = { Name = "example" } } # インバウンドルール(ssh接続用) resource "aws_security_group_rule" "in_ssh" { security_group_id = aws_security_group.example.id type = "ingress" cidr_blocks = ["0.0.0.0/0"] from_port = 22 to_port = 22 protocol = "tcp" } # インバウンドルール(pingコマンド用) resource "aws_security_group_rule" "in_icmp" { security_group_id = aws_security_group.example.id type = "ingress" cidr_blocks = ["0.0.0.0/0"] from_port = -1 to_port = -1 protocol = "icmp" } # アウトバウンドルール(全開放) resource "aws_security_group_rule" "out_all" { security_group_id = aws_security_group.example.id type = "egress" cidr_blocks = ["0.0.0.0/0"] from_port = 0 to_port = 0 protocol = "-1" }
terraform applyでec2を立ち上げssh接続を行う
terraform apply # yesを実行する
# EC2が立ち上がったら # インスタンスのパブリックIPをチェックする ssh -i id_rsa_ec2 ec2-user@インスタンスのパブリックIP
#EC2インスタンスを破棄する terraform destroy
djangoのcontextをjavascriptに渡す際に困る場合がある件
大した内容ではないので問題点と対処法を軽く書く。
djangoのcontextに格納したデータをjavascriptにわたす方法
- 最初にviewにてデータをcontextに格納する
- テンプレートにてjavascriptを走らせ、contextのデータを受け取る
def example(self, request, *args, **kwargs): context = {} context["data"] = "my_data" return render(request, "hoe/hoge.html", context)
テンプレートのscriptタグ内でdataをを取り出す。
<script> var data_of_django = "{{ data }}"; console.log("{{ data }}") </script>
こんな感じでscriptタグ内でdjangoのcontextデータを取り出し加工したりして使っていくのが基本。
問題となるパータン
モデルの属性としてTextAreaフィールドを設定している場合がある。 TextAreaは改行もデータとして保存できるが、この改行がjavascriptではエラーの原因になってしまう。
対処法
javascriptに渡すデータをjsonに加工すること、またテンプレートでは"|safe"を使って取り出すこと。この2点で対処できる。
import json def example(self, request, *args, **kwargs): context = {} context["data"] = json.dumps("my_data") #ココ return render(request, "hoe/hoge.html", context)
<script> var data_of_django = "{{ data|safe }}"; console.log("{{ data|safe }}") </script>
leafletを使って以下が表示される。"This content should also be served over HTTPS."
問題は何なのか
leafletを使っていて以下の内容がconsoleに表示される事になった。
Mixed Content: The page at '<URL>' was loaded over HTTPS, but requested an insecure image '<URL>'. This content should also be served over HTTPS.
これは何を示しているかというと、表示されたページがHTTPS通信でページが表示されているにも関わらず、安全ではないHTTP通信で通信されている。
この部分についてもHTTPSで配信するべきだ。って旨である。
で、該当するURLは"http://c.tile.osm.org/13/2014/3754.png"であった。
osm.orgからの配信内容である。
自分の場合leafletを使ってopen street mapを配信するときにこれが表示される事がわかった。
osmをhttps化して配信する資料
Streaming Leaflet Tiles over HTTPS / SSL · Issue #3186 · Leaflet/Leaflet · GitHub
資料の概要
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' }).addTo(map);
上のコードを下のコードに変更する。
L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', { attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' }).addTo(map);
要するに"http://{s}.tile.osm.org/{z}/{x}/{y}.png"の部分を'https://{s}.tile.osm.org/{z}/{x}/{y}.png'に変更するだけといったもの。
自分のコードを直してみる
自分のコードを確認してみると、、、
var map = L.map('map_leaflet', { center: [lat, lon],zoom: 13}) .addLayer(L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png'));
確かにhttpから配信する仕組みになっている。これをhttpsに変更してみようと思う。
'https://{s}.tile.osm.org/{z}/{x}/{y}.png'に変更したところうまく行った。
他にも 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'で成功している人もいるみたいだけれども自分の場合はhttps://{s}.tile.osm.org/{z}/{x}/{y}.png'でうまく行った。
またleafletのcss,javascriptを直接ダウンロードしてnginxから直接配信する方法でもhttps化できるという意見もあった。
supervisorについてメモ
ドキュメント
Supervisor: A Process Control System — Supervisor 4.2.0 documentation
supervisorは何なのか?
これはプロセス管理のツールである。
fabricを使った自動デプロイスクリプトを書いては見たもののgunicornのプロセスを切ってから新たに立ち上げることにたいして思うように制御することができなかった。そこでsupervisorを使う事になった。
supervisorのイメージ
supervisorの構成はドキュメントでは以下の構成とされている。
supervisord supervisorctl Web Server XML-RPC Interface
まだよく分かっていない状態でこれらの役割を敢えていうとすると、supervisordはクライアントから命令を受けるためのサーバーでありsupervisorctlはそのサーバーに命令を送るためのクライアントである。このクライアントはshellのようなインターフェースをもって命令を送ることになる。Web Serverはwebのコントロールパネルから制御するものなのかと仮設を立てているが使っていないのでよくわからない。XML-RPC Interfaceは全くわからない。
自分が言いたいことはsupervisordとsupervisorctlさえ使えばdjangoのアプリケーションのプロセス管理ができるということだ。
djangoのプロセス制御をどのように行ったか
上で説明したようにsupervisordとsupervisorctlさえ使えばプロセス管理ができる。両者を使うには諸設定ファイルが必要なのはもちろん、linuxでは標準実装されていないのでインストールが必要になる。またsupervisorの立ち上げ、supervisorctlを使ってプロセスをkill、アプリケーションの立ち上げを行う。これらをできるだけ順を追って再現できるように振り返りたい。
0. 前提
- supervisor 4.2.0
- centos7
1. インストール
supervisorをインストールする。これはpipを使う方法とepelからyum intastallみたいな形でインストールすることもできるようだ。自分はpipからインストールした。 popのほうがより新しいバージョンのsupervisorを使うことができるからだ。
pip install supervisor
2. 設定ファイルを作成
supervisordとsupervisorctlは設定ファイルによって制御されている。この設定ファイルは同一のファイルから両者を制御することもできるし、supervisorctlのコントロールしたいプロセス毎に作成し、それとsupervisordの設定ファイルということもできる。詳しいことは設定ファイルの編集で説明する。
pipでインストールしただけでは設定ファイルが存在しないので設定ファイルを作成する。
設定ファイルは雛形が存在しており、コマンドを使ってその雛形を呼び出し設定ファイルとする。
#supervisorの設定ファイルを/etc配下に作成する sudo touch /etc/supervisord.conf sudo echo_supervisord_conf > /etc/supervisord.conf
echo_supervisord_confのコマンドはsupervisorをインストールすると使えるようになっており、この中に設定ファイルの雛形が書いてある。この中身を設定ファイルにコピーする流れになる。
また設定ファイル名は上記のようにsupervisord.confが望ましいし、当該ファイルの配置場所も/etc/以下に配置するのが望ましい。 それは基本的に-cオプション(設定ファイルの指定)を付さないでコマンドを入力すると、supervisorはsupervisord.confという設定ファイルを読み込もうとする仕様であるからだ。また、設定ファイルの配置する場所も-cオプションがなければ以下のディレクトリから設定ファイルを探す仕様になっているからだ。
../etc/supervisord.conf (Relative to the executable) ../supervisord.conf (Relative to the executable) $CWD/supervisord.conf $CWD/etc/supervisord.conf /etc/supervisord.conf /etc/supervisor/supervisord.conf (since Supervisor 3.3.0)
3. 設定ファイルの編集
作成した/etc/supervisord.confに手を加えるとsupervisordとsupervisorctlが使えるようになる。試してはないので確信が持てないがsupervisord.confにsupervisordとsupervisorctlの両方の内容を記述して動かすことができる。
自分の場合はsupervisordについては/etc/supervisord.confに記述し、supervisorctlで管理したいプロセスは別途設定ファイルを作成し、
/etc/supervisord.confから当該設定ファイルの読み込みませる仕組みにした。したがっていまから行うのは/etc/supervisord.confの編集とsupervisorctlで管理するプロセスに関わる設定ファイルの作成である。
ⅰ. /etc/supervisord.confの編集
supervisordに関する設定内容。ここは設定すると言ってるが全く触らなくてよかった。雛形のママである。
このファイルだとlogfileやpidfileが/tmp以下に作成されるので何らかのエラーが生じた場合は/tmp以下のlogを閲覧することになる。ココは任意に変更すると良いだろう。
[supervisord] logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 loglevel=info ; log level; default info; others: debug,warn,trace pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid nodaemon=false ; start in foreground if true; default false silent=false ; no logs to stdout if true; default false minfds=1024 ; min. avail startup file descriptors; default 1024 minprocs=200 ; min. avail process descriptors;default 200 ;umask=022 ; process file creation umask; default 022 ;user=supervisord ; setuid to this UNIX account at startup; recommended if root ;identifier=supervisor ; supervisord identifier, default is 'supervisor' ;directory=/tmp ; default is not to cd during start ;nocleanup=true ; don't clean up tempfiles at start; default false ;childlogdir=/tmp ; 'AUTO' child log dir, default $TEMP ;environment=KEY="value" ; key value pairs to add to environment ;strip_ansi=false ; strip ansi escape codes in logs; def. false ; The rpcinterface:supervisor section must remain in the config file for ; RPC (supervisorctl/web interface) to work. Additional interfaces may be ; added by defining them in separate [rpcinterface:x] sections.
同ファイルの最下部に[include]がある。これを編集(追記)する。
[include] files = /etc/supervisord.d/*.conf ;(追記) 起動するプロセスのconfファイルの配置場所 ;files = relative/directory/*.ini
ⅱ. プロセスのconfファイルを作成
プロセスのconfファイルを配置するディレクトリを作成。
sudo mkdir /etc/supervisord.d
プロセスのconfファイルを作成
sudo touch /etc/supervisord.d/django_project.conf
プロセスのconfファイルの編集
sudo vi /etc/supervisord.d/django_project.conf
で以下のように記述する。
[program:django_project] ;この値を引数としてsupervisorctlを使う directory=/home/user_name/src command=gunicorn config.wsgi --bind localhost:8000 --env DJANGO_SETTINGS_MODULE=config.prod_settings --daemon config.wsgi:application numprocs=1 autostart=true autorestart=true user=user_name ;デプロイするユーザー名 redirect_stderr=true
4. supervisordの起動とプロセスの実行
supervisorの起動する supervisord
設定ファイルの読み込み supervisorctl reread
プロセスのリスタート supervisorctl restart django_project
一発でうまく起動することが自分はできなかった。自分の場合はすでに8000番ポートを使ってアプリケーションを起動していた。うまくイカない場合はすでに起動しているdjangoのプロジェクトのプロセスをkillしてから実行してみるとよい。
sudo lsof -i:8000
pidの番号をkillする
kill -9 番号
Djangoのformをより良く見せるために(改訂版)
Djangoにおいてformウィジェットの理解を深めない限り、forms.ModelFormやforms.Formの利用したとて、フォームを使った画面が残念な結果になってしまう。
見た目の良いフォーム画面を作るには
- htmlファイルを自ら作成して好みのformを作成する
- ModelFormやFormのウィジェットをforms.pyにて編集する
- javascriptを使ってDOM操作を行う
- サードパーティライブラリを使用する
これらの方法が考えられるがメリット、デメリットが存在する。 これらのメリット・デメリットを考えてみたい。
そもそもウィジェットとは?
現在自分の中ではウィジェットとはformだったりボタン等のユーザーインターフェースのことを指すと考えている。
htmlファイルを自ら作成して好みのformを作成する方法
これはテンプレートにformタグと共にインプットタグ等を書いていく方法で、class属性を思うままに書くことができるので自由に作ることができる。
しかしながらdjangoに備わっているインプットデータのバリデーション機能を享受できなくなるデメリットがある。自分でバリデーション機能も実装するのは手間なので避けるべき方法だと思う。
ModelFormやFormのウィジェットをforms.pyにて編集する方法
関連ドキュメント
https://docs.djangoproject.com/ja/2.2/topics/forms/
https://docs.djangoproject.com/ja/2.2/ref/forms/widgets/
例えばBootstrap4のform-controlを反映させたい場合以下のようになる。
class CommentModelForm(forms.ModelForm): class Meta: model = Comment fields = ("name","url","comment") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): self.field["name"].widget.attrs["class"] = "form-control" self.field["url"].widget.attrs["class"] = "form-control" self.field["comment"].widget.attrs["class"] = "form-control"
その他ウィジェットを編集する方法は以下のようになる。なお、参考資料は以下である。 https://docs.djangoproject.com/ja/2.2/ref/forms/api/#accessing-the-fields-from-the-form
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field in self.fields.values(): self.field["name"].required = True #必須項目のフォームに変更する self.field["url"].label = "URL" #ユーザ視点でフォームの入力名が変わる。 self.field["comment"].widget.attrs["placeholder"] = "コメントを書いてください。"
forms.pyのinitでwidgetを編集する方法である。これを使うとformのwidgetの属性値を管理するのが楽になる。forms.pyを直接いじれない場面に遭遇するといじるために手間がかかる。またファイルは違えどviews.pyで編集する方法もこの方法に含まれる。
Javascriptを使ってformを改善させる方法
上記の方法はforms.pyにおいてformのインスタンス初期化時に属性をセットする方法である。
サードパーティのライブラリを使う場合に問題が顕在化する。例えばdjango-allauthではログイン等の画面も準備してくれるが、そのフォームを編集する際にforms.pyをいじることが多少難しくなることだ。実際いじることはできるが少し複雑になってしまうので構成を共有して理解するには難しいと思われる。
こちらの記事の一部にformの編集の仕方を書いている。
django-allauth : テンプレートのカスタマイズ - diadia
このような場合にはjavascriptを使ってDOMを操作することが良い思う。 共通のformを利用する場合にはincludeを挿入すればよいだろうし、コンポーネント化できるので積極的に使って良い方法だと思われる。問題点はjavascriptの知識が要求されることである。
Vue.jsとか他のフレームワークを使っている場合DOM操作がバッティングする恐れがあり、それらがどのように反応するかは検証する必要がある。自分の場合それがどのように反応するかは分かっていないのでそこをはっきりさせるのにコストがかかる。
window.addEventListener('load', function() { var email_form = document.getElementById("id_email") email_form.setAttribute('class', 'form-control'); })
サードパーティライブラリを使用する方法
django-widget-tweaksというライブラリが存在する。
https://python.keicode.com/django/form-add-class.php
これを使えばテンプレートタグに追加したい属性値を直接記述できるようになる。サードパーティライブラリなのでメンテナンスされるかが外部に依存する事になってしまう。
{% load widget_tweaks %} {{form.username|add_class:"form-control"}}
結論
個人的にはforms.pyのインスタンスの初期化で各ウィジェットを調整し、それが難しい場合にはjavascriptを使って調整する方法を取るのが望ましいと思う。
django ページネーションについて(改訂版)
ページネーションについて
ページネーションは一覧表示させるオブジェクトを1ページ内にどれだけ分割して表示させるか。それに関わる技術のことのようだ。
関連ドキュメント
ページネーション | Django ドキュメント | Django
メモ
djangoにおけるページネーションに関わるオブジェクトは2つある。
このオブジェクトの役割は具体的にはなんなのか?
Paginator オブジェクトはどんなページネーションにするかを定めるために使うのと、そのオブジェクトを使ってページを表示することができる(もちろん他にも)。
Page オブジェクトはあるページに該当するデータを参照したり、その隣(前のページや後のページ)が存在するか、その隣のページを参照するような役割をもつ。
1ページ当たりいくつのオブジェクトを描画するかを定めるのがPaginatorの役割で、それはsettings.pyでページネーションを定めるのではなくviewsの各ロジックに組み込んで使用する。 またPageオブジェクトはフロントエンド側で使いやすいと思われる。具体的には隣のページがあればページを繰るアイコンを表示させるとかクリックすることで該当ページの内容を表示させるとか。 もちろんPageオブジェクトをテンプレートで使いたいのでviewsでPageオブジェクトを生成しcontextに格納することを想定される。両オブジェクトはともにviewsで使用されることか2つのオブジェクトを組み合わせた関数を作ってしまうと便利かも。
以前の記事
ページネーションの必要素は2要素のようだ。表示するべきリストオブジェクトの作成とのページを繰る関係のオブジェクトである。で前者はPageクラスインスタンスにobject_list属性で表現できるようだ。
ページネーション | Django ドキュメント | Django
後者については同じくPageクラスインスタンスに属性やメソッド使ったりすると表現できるようだ 。
ページネーション | Django ドキュメント | Django
page_obj.object_listは普段object_listをhtml上に表現するように扱えばいいので特に問題はないだろう。page_obj.numberについて考える。まずpage_obj.numberはどこに使うか?という問題である。言うまでもなくviews.pyのrequest.GETに関するコードの流れの最終着地点はcontextに追加し、render関数を介してhtml上に表示させることだ。だからpage_obj.numberはhtmlに表現される。htmlの下部の"前へ","1","2","3","次へ"というものだ。page_obj.numberはここに使われる。またpage_objには以下のメソッドがある。これらを使うと前へ、とか"3"を表現できるようになりそうだ。
Page オブジェクトメソッド | 動作 |
---|---|
page_obj.has_next() | 次のページが存在する時Trueを返す。 |
page_obj.has_previous() | 前のページが存在する時Trueを返す。 |
page_obj.has__other_pages() | 次のページまたは前のページが存在する時Trueを返す。 |
page_obj.has_next_number() | 次のページ数を返す。次のページが存在しない場合はInvalidPage例外を起こす。 |
page_obj.start_index() | ページ上の最初のオブジェクトに対する、1から始まるインデックスを返します。これは、ページネータのリストに含まれる全オブジェクトに対するインデックスです。たとえば、5個のオブジェクトのリストを各ページ2オブジェクトでページネーションしている場合、2ページ目の start_index() は 3 を返すでしょう。 |
page_obj.end_index() | ページ上の最後のオブジェクトに対する、1から始まるインデックスを返します。これは、ページネータのリストに含まれる全オブジェクトに対するインデックスです。たとえば、5個のオブジェクトのリストを各ページ2オブジェクトでページネーションしている場合、2ページ目の end_index() は 4 を返すでしょう。 |
だからhtmlではpage_obj.object_listの使い方としては、
{% for obj in page_obj.object_list %} {{ obj }} {% endfor %}
ページを繰る関係で言えばhtmlでは以下のように表現できる。
{%for n in page_obj.paginator.page_range %} <a href="?page={{ n }}">{{ n }}</a> {% endfor %}
page_objの作り方
page_objはPageクラスのインスタンスである。しかしながらpage_obj=Page()と作るわけではないようだ。作り方はpage_obj = pagenator_obj.page(number)で作る。pagenator_obj=Pagenator()で作成する。
cdnでvuetifyを使うために
軽く要点を残す。
djangoでincludeを使ってコンポーネント化できたのでテンプレートの見通しが良くなった。
cdnを使ってコンポーネント化するとvuecliを入れる前にどんな感じでvuetifyや単一ファイルコンポーネントが使えるか確認できる。(もちろん全く同じではない。)
完成形イメージ
<body> <my_template/> {% include "my_template_vuejs.html" %} </body>
"my_template_vuejs.html"でmy_templateタグの定義を行うとともに、vueの初期化でmy_templateタグを使えるように書く。そしてmy_templateタグを表示させたいところに配置する。
必要なこと
1 vue.jsが起動するテンプレートタグを設定する。
<body> <my_template/> #これ </body>
2 "my_template"タグの定義を書く
cdnでテンプレートを定義するときに注意しなければならないことがある。それは必ずscriptタグから書き始めること、templateタグを内部で書かないことである。特にtemplateタグを書いてしまうとエラーとなってしまうので注意が必要。 またこのような書き方はインラインテンプレート記法と呼ぶようだ。Vue.js初心者向け:インラインテンプレートから単一ファイルコンポーネントの使い方まで - Qiita
touch hoge/my_template_vuejs.html
<script type="text/x-template" id="m_temp"> <p>自分の書きたいコンポーネントを書く。 ここにvuetifyのテンプレートを直接書き始めても正常に読み込まれる。</p> </script>
3 同一ファイル内でvueの初期化を実施する
<script> Vue.component("my_template", #呼び出したいテンプレート名 { template: "#m_temp", #上で定めたコンポーネントのid }); new Vue({ el: "m_temp", vuetify: new Vuetify() #Vuetifyを使う場合これを追加する }) </script>
あとはインクルードで呼び出せば使える。
cdnでコンポーネント化する資料はココにもあった。こっちのが書き方が明快かも CDNで始めるVue.jsコンポーネント③ - Qiita
nginxで403が返されるときに対応したこと
サーバー上に新しくディレクトリを作成し、そこを起点にgithubからpullを行いデプロイした際にうまく作動しなかった。
環境
centos7 nginx porstgresql django
症状:
- home画面のhtmlはサーバーから配信される。
- home画面の静的ファイルの配信がうまく行っていない。
- 配信がうまく行かなかった静的ファイルに直接アクセスする(新しいタブで画像を開く)場合403が返される。
判断:
home画面のhtmlはサーバーから配信される。 -->
webサーバーとWebApplicationSerer(django)は正常に作動していると可能性が高い。
home画面の静的ファイルの配信がうまく行っていない。 -->
webサーバー(nginx)から静的ファイルを送信する際に問題がある。
(新しいタブで画像を開く)場合403が返される。-->
nginxが静的ファイルにアクセスするが権限に関して問題がある可能性を示唆している。
対応したこと
- "nginx 403"で検索をかけ、以下の記事を探し当てた。 Nginx で 403 Forbidden エラーが出るときのチェック項目|まくろぐ
- ドキュメントルートまでに実行権限があるかチェックした。
- うまく配信されなかった静的ファイルの読み取り権限があるかチェックした。
なお、権限チェックに関してはlsコマンドを使った。
ls -l (対象ディレクトリ)
ドキュメントルート(静的ファイルを配信するために静的ファイルを集めたディレクトリ)までの実行権限を調べたところ見事に実行権限がなかった。具体的にはドキュメントリート自体には実行権限xが存在していたが、親のディレクトリとその親ディレクトリにはxの権限が欠如していた。 rootからドキュメントルートまで下記のコマンドを実行した。
chmod +x (対象のディレクトリ)
問題のあった静的ファイルの読み取り権限rはあったのでこれに関して特に行うことはなかった。
そして再度nginxの起動を行った。
systemctl restart nginx
結果として正常に静的ファイルを表示させることができた。
この件から得られた知見
nginxがファイルを配信する前提としてドキュメントルートまでの実行権限が必要なこと、配信する静的ファイルに読み取り権限が必要なことを学んだ。またnginxで403が出る場合とその他のエラーが出る場合を経験したけど、そもそもhome画面が表示されない場面に出くわした。このときは静的ファイルがドキュメントルートに存在していない場合があった。
だからnginxの配信するファイルの存在があるかないか ?
無い --> 画面自体が表示されない。
ある --> 権限に問題がある。
今の所こんな感じの原因を推測できる
AWS学習メモ
知識の整理の方法をよく考えたい。
実現したい実装に対して、概念による実装方法があり、具体的な実装方法がある。
具体的な実装方法はコンソールによる具体的な実装方法とteraformによる実装方法である。これらの棲み分けを意識して分けてメモをしていきたい。
EC2のパブリックIPアドレスがほしいときは、ec2インスタンス生成時に"自動割当パブリックIP"という項を有効化しなければならない。デフォルトだと有効可になってないので注意。そしてネットワーク内部で通信するときにはネットワーク・インターフェースという項で自分の好きなプライベートIPを設定する事ができる。
VPC: Virtual Private Cloud
ネットワークの基盤を作るサービス
awsマネジメントコンソールの場合
VPCを作成するときにはVPCサービスから選択してVPCを作成する。
Terraformの場合
resource"aws_vpc" "example"{ cidr_block="10.0.0.0/16" enable_dns_support=true enable_dns_hostnames=true tags={ Name="example" } }
VPC内にサブネットを作成するけれども、これはどのようにするか?サブネットはVPCサービスの項目の一つであるので、VPCのダッシュボードから作成することになる。サブネット作成時の留意点は、どのVPCと紐付けるかを確認することと、サブネットのCIDRの値を定めることである。
ルーティングの設定
インターネットゲートウェイもルートテーブルもVPCのダッシュボードの項目の一つとして存在している。各項目を選んで各々作成する運びとなる。
要素 | 割り当てる、アタッチ対象 |
---|---|
サブネット | VPC |
インターネットゲートウェイ | VPC |
ルートテーブル | サブネット |
|セキュリティグループ|EC2インスタンス|
要素 | 何を設定スべき |
---|---|
VPC | ネットワークの構成 |
サブネット | サブネットの領域 |
ルートテーブル | インターネットに接続したい場合はこちらにインターネットゲートウェイの情報を登録する |
セキュリティグループ | IPとポートを制限しているが、sshやhttp,DBの接続用にポートを開けること考えなければならない。 |
fdfs | vdvd |
要素 | どう設定するか |
---|---|
ddd | df |
EBS
EC2のストレージ扱いっぽい。EC2を停止していてもこの部分に関しては課金され続ける。
セキュリティグループ
セキュリティグループってのはファイアウォールに当たる言葉のようだ。
EC2接続方法
macの場合はssh接続を行う。ダウンロードしたキーを使ってssh接続を行う。 注意点はログインユーザー名をec2-userとしてログインを実行する。
chmod 600 ~/Desktop/aws-keypair.pem ssh -i ~/Desktop/aws-keypair.pem ec2-user@54.245.191.191 -p 22
アパッチのインストール、セッティング
インストール
sudo yum update -y sudo yum install httpd -y
起動
sudo systemctl start httpd sudo systemctl enable httpd sudo systemctl status httpd
RDSについて
これもAWSのサービスの一つ。データベース関連のものでpostgresql, mysql, mariadb, oracle, sql server等を使うことができる。 DBをセッティングする方法としてオンプレミス、onEC2 、RDSの3通りが考えられる。onEC2とは、さくらVPCのサーバー上にDBをインストールさせるのと同じようにEC2インスタンス上にDBを設けることだ。
RDSを始める
まずRDSは可用性を高めるためにサブネットを2つ準備しなくてはならない。したがって、VPCのメニューからサブネットを2つ作成する。 次にRDSに接続するためのセキュリティグループをRDS作成時に求められる。これも作成しなければならない。 あとwebサーバーとDBサーバーを分ける場合にはRDSに接続するためにwebサーバー側にDBのクライアントをインストールする必要がある。
またRDSもEC2と同様従量課金制なので使用しないときはRDSを停止することが望ましい。
[速報] RDSインスタンスの起動/停止 | Developers.IO
MYSQL
インストール
sudo yum -y install mysql
接続
mysql -h (endpoint: RDSに書いてある) -u (username: RDS作成時に定めた値) -p
このあとパスワードを入力して認証されれば、RDSに接続できたことになる。 またssh接続する事はできない。
ユーザー作成
CREATE USER '(ユーザー名)'@'%' IDENTIFIED BY 'password';
作成したユーザーにDBへの権限を付与する
GRANT ALL ON (DB名).* TO '(ユーザー名)'@'%';
与えた権限を反映させる
FLUSH PRIVILEGES;
SELECT user, host FROM mysql.user;
terraformのリソースタイプ(確認している限り)
- resource"aws_instance"
- resource"aws_security_group"
- resource"aws_vpc"
- resource"aws_subnet"
- resource"aws_internet_gateway"
- resource"aws_route_table"
- resource"aws_route"
- resource"aws_route_table_association"
- resource"aws_eip"
RDSについて
料金の請求はRDSインスタンスを削除していても請求されることがあるから注意。必ず削除したRDSに関連したスナップショットも削除すること。これで請求されなくなる。
fabricでデプロイするために得られた知見
まずfabricでできることはなにか?
fabricでできることはshellスクリプトでもできる。シェルスクリプトが書ければfabricをあえて学習する必要ないと思われるが、fabricを使うのはpythonを使うので学習コストが低いし、pythonで書くのでコードの見通しが良くなりメンテナンス性が向上する。
そもそも現状の自分ではシェルスクリプトを満足に書くことができない。
fabricを使ってdocker-composeコマンドでデプロイするか、デプロイに必要な各コマンドを叩くかの選択肢がある。
自分の場合すでに稼働させているサーバーに対して各コマンドを叩いてデプロイを実施する。
fabricバージョン2と1がある。使い方がぜんぜん違うようだ。 旧使い方と新使い方でどのような違いがあるかチェックしたい。
基本的な使い方
fabricのスクリプトをfabfile.pyとして記述する。fabfileというディレクトリを作成するパターンも見られル。
fabricをインストールするとfabコマンドを使うことができる。
fab fabfileの関数
これを使ってデプロイを実施していく。
fabfileの中で行うことは以下である。
- githubまたはbitbucketからコードをpullする。
- pullしたコードの中にあるrequirements.txtを読み込み、依存関係のあるパッケージをインストールする
- python manage.py makemigrationsを行う
- python manage.py migrateを行う
- python manage.py collectstaticを行う
- サービスを新たにする。
今までコードのpull, cloneはhttps通信を使っていた。この問題点はリポジトリにアクセスするとパスワードを入力される。この要求をパスしないとデプロイができないのでパスワードを聞かれないような方法でpullを実行するように変更しなければならない。 これについてはgithubやbitbucketにssh接続するとパスワードを要求されなくなることがわかった。
ではssh接続するためには何が必要か?
- pullを行うサーバー側においてリモートリポジトリの登録をsshバージョンで登録する
- サーバーにおいてssh接続を行うための秘密の鍵、公開鍵を生成する
- 上で生成した公開鍵をgithub, bitbucketに登録する
これをするとパスワードを要求されずにpullを実行する事ができる様になる。
軽くメモをすると、 git remote -vで表示されるリモートリポジトリの表示をsshバージョンに変更するには以下のコマンドを実行すれば良い。
git remote set-url origin git@github.com:[ユーザー名]/[リポジトリ名].git
githubで公開鍵を登録する際にはリポジトリごとに登録すると考えていたが、アカウント毎に登録する仕様になっていることが分かった。だからいくらリポジトリからsshキーの登録を探そうとしても見つからない。。。
以下にアクセスすること。
https://github.com/settings/keys
参考:
git パスワード を毎回聞かれる問題の解決方法 - Qiita
GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~ - Qiita
ディレクトリやファイルが存在するかどうかをチェックする場合について
fabricのrun関数の引数としてshellスクリプトの断片を書きまくれば良いと考えていたが、そうではなかった。fabricを使ってディレクトリの存在を確認するには以下の関数をインポートして使うようだ。そしてこれはshellスクリプトを書かないで済むからメンテナンスしやすくなってありがたい。。。
from fabric.contrib.files import exists def is_exist_dir(self): with cd("/"): if exists('/home/user/tet_dir'): print("存在する") else: print("存在しない")
fabricディレクトリの存在のチェックするには?の資料
python - Check If Path Exists Using Fabric - Stack Overflow
あと今思うのはcircleciを使わなくてもfabricだけで完結したほうがスッキリするし、circleciを利用する意義について理由を探さなければならないと思うくらい便利。
例えばgit pushを行うのはすべてfabric経由で実行する。
fabric内でテストを実行する。テストで失敗すればそのままデプロイがされないし、テスト成功したらデプロイ実行させる。
これで十分なんじゃないか...?
fabricでgunicorn のdaemonオプションが使えない時
--daemonをつけないと適切にgunicornが実行されるのに--daemonをつけたときだけうまく作動しない症状に出くわした。 これについては以下が参考になった。 Fabricでgunicornのデーモン化ができなかったアレ - Qiita
結論から言うとpty=Falseを付け加える。
run("/usr/local/bin/gunicorn --daemon --env DJANGO_SETTINGS_MODULE=config.settings.prod_settings config.wsgi:application -c {}".format(GUNICORN_CONF), pty=False)
circleciのsshキーを登録する
circleciのコンソールにsshキー(秘密の鍵)を登録する事ができる。
しかしこの秘密の鍵はpem形式でなければコンソールでエラーが出てしまう。したがってpem形式のキーを別途作成し登録する必要があった。
手順; 1. pem形式の鍵を作成する 1. サーバーに公開鍵を登録する 1. 秘密鍵をcircleciに登録する
ssh-keygen -f hoge -m PEM
参考資料
SSHの鍵 - ふなWiki
CircleCIのメモ
CircleCI環境変数について
circleciの環境変数の定め方は2通りある。
一つはconfig.ymlで環境変数をハードコーディングで定める方法である。
もう一つはcircleciのコンソールで環境変数をキーとバリュー形式で登録しておき、config.ymlでキーを呼び出すことで環境変数を読み込む方法である。
config.ymlでハードコーディングする方法は例として以下のようになる。
version: 2 jobs: build-job: docker: - image: circleci/python:3.7.3 - image: circleci/postgres:10.5-postgis environment: DATABASE_URL: postgres://postgres:@localhost/circle_test SECRET_KEY: aaq1sjddijfv*d5j%$ssyji+@!2bqur_hynpls_fwozwbs9rkj DJANGO_READ_DOT_ENV_FILE: True DJANGO_SETTINGS_MODULE: config.settings.dev_settings working_directory: ~/app
またシェルコマンドで環境変数を設定することもできるが、単純に
export PATH=/path/to/foo/bin:$PATH
を使えばよいというわけではない。この方法では環境変数を設定する事ができないからだ。
echo 'export PATH=/path/to/foo/bin:$PATH' >> $BASH_ENV
BASH_ENVを使う必要があることに留意すること。
シェルコマンドによる環境変数の設定の資料
今の所config.ymlから.envファイルを読み込む方法が使えるのか確認できていない。 ※ docker-compose.ymlではenvironmentで環境変数を定めることができるに加えて、env_fileで.envファイルを読み込むことができた。分かったら後に書き加える。
キャッシュについて
依存関係のキャッシュ
キャッシュを使うとビルドにかかる時間を短縮できる -> circleciで利用できる無料利用分を有効に使うことができる。
どんな仕組みでキャッシュを更新するのか??
どう使うのか??
config.ymlで記述するのはstepsでrestore_cacheやsave_cacheを使う。
取得済みのデータが有ればrestore_cacheを宣言してキャッシュ済みのキーをリスト形式で記述すればキャッシュの読み込みができる。
steps: - checkout - restore_cache: key: pip-{{ checksum "requirements.txt" }}
checksumはキャッシュが更新するかを確認するためのもの。checksumの値が変更されていれば後述するsave_cacheでキャッシュを更新する。
- save_cache: key: pip-{{ checksum "app/requirements.txt" }} paths: - 'venv'
workflowsについて
ジョブの実行を Workflow で制御する - CircleCI
よく使うコマンド
circleci local execute --job ジョブ名
circleciコンソールの見方
circleciの実行を止める種類は2つある。
workflowsそのものをとめる方法と実行中のjobを止める方法が存在する。
workflowsを止める方法はプロジェクトを選んだ状態の画面から"Return"というドロップダウンメニューから"Cancel workflow"を押すことで止める事ができる。
一方jobを止める場合はworkflowsからjobをクリックした画面(jobの詳細実行画面)にある"Return"というドロップダウンメニューに"Cancel job"がある。これを押すことでjobを止めることができる。
ssh接続ができる状態にしてからどうやってssh接続できる状態を止めるようにするかはまだあやふや。これをきっちり止めないと新規に新たな実行ができないままになってしまう。無料枠だから??
CircleCIで実行サーバーにssh接続する際に困ったこと
ビルドしてテストして問題がなかったらデプロイに移るわけだけれども、ssh接続できずにずっと苦しい思いをした。 ココについて解決できたので改めて整理してみたいと思う。
必要なこと。
githubからソースコードを取得するのは2つの場面がある。一つはgithubにpushすることでcircleciサーバーが起動し取得する場面である。2つ目はテストが完了し実行サーバーでデプロイする際にコードを取得する場面である。
circleciサーバーがコードをpullする際はsshkeyが必要になるが、特にcircleci側にsshkeyを登録する必要はない。
サーバー側でデプロイするときにはhttps通信でpullを実行する場合とssh通信でpullを実行する場合がある。httpsでpullを実行する場合には認証するためにpasswordをいんてラクティブな形式で入力することになる。これをcircleciでデプロイする場合にはpasswordを入力する事ができず、デプロイ作業中に止まってしまう。passwordを聞かれずにpullさえできればデプロイ作業が継続される。ssh接続でpullを実行するとpasswordを聞かれることがない。
したがって実行サーバーがpullを行うときはssh通信を行うことが必須となる事がわかった。これを実現するには以下2件の作業が必要になる。
実行サーバーがpullしてきたものに対しgithubがpullを許容するために、別の言い方をすれば実行サーバーからgithubにssh通信を実行するためには、通信を開始する側は秘密の鍵が必要になるし、通信を受ける側であるgithubでは秘密の鍵が必要になる。 したがって実行サーバーで秘密の鍵、公開鍵を作成し、公開鍵の情報はgithubに登録する作業が必要になる。
またgit remote -v で表示される形式がhttps形式であれば実行サーバ-に秘密の鍵があろうとまたgithubに公開鍵があろうとhttps通信が行えわれ結果としてpasswordが聞かれてしまう。そこでssh通信を行う宣言をするためにもssh形式にあったリモートの登録が必要になる。
この辺のことはいかにうまくまとめられている。
GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~ - Qiita
これでcircleciからgithubの接続と実行サーバーからgithubの接続ができるようになった。 circleciでpullを行いビルド、テストを実行する。 circleciサーバーから実行サーバーにssh接続を行い、実行サーバーがgithubからpullを実施する。ここでまだcircleciサーバーから実行サーバーに接続する設定が終わっていない。
circleciサーバーから実行サーバーに接続するにはやはりsshの鍵が必要でそれに関連した作業がある。
- pem形式のsshkeyを作成し、circleciサーバー側に秘密の鍵を登録する
- ssh接続の際にオプションを付け加える。
sshkey-genコマンドを実行する際にpem形式の鍵を作成しないとcircleciで秘密の鍵を登録する事ができない。
したがって-m pemオプションをつけて実行する。
sshkey-gen -m pem
ssh接続の時新たなサーバーに接続する場合にはsshクライアント側で以下のような質問が標準出力される。
ECDSA key fingerprint is SHA256:IcFXfHgh42HvcZiG/zQmle+IjD4lrUu4XV+469JheJE. Are you sure you want to continue connecting (yes/no)?
この出力に対し、yesを入力しなければssh接続が開始されない。これはそもそも接続先として誤ったhostに接続した場合に誤ったホストはsshクライアントが入力する内容を盗聴する事ができる。このような場合にクライアントがsudo コマンドでpasswordを入力したものなら悪用されることに怯えなければならない。したがって以前通信したことがないホストと通信する際に上記の標準出力が表示されるのはあなたが接続しようとしているホストは間違っている可能性もあるので注意してねって感じの注意喚起の役割を果たしていることが分かった。
この標準出力を出さない方法として
ssh -p 22 -o 'StrictHostKeyChecking no' username@host
がある。これを実行すればssh接続できる。
circleci+fabricの資料
これがとても良くわかりやすくベースはこれを使って実装した Django and CircleCI 2.0 | Timmy O'Mahony