K4750.NET

オライリーの「Docker」のサンプルが動かなかったので修正してみる

約2年前の2016年8月に発行されたオライリー「Docker」。5章「開発でのDockerの利用」を実際に手を動かして確認してみたところ、所々動かない個所があったので修正してみる。


1.「5.1 “Hello World!”」

コンテナ内で実行してみたがエラーになる・・・どうやら2行目がインデントされているのが問題のようだ。

from flask import Flask
    app = Flask(__name__) 

@app.route('/') 
def hello_world():
    return 'Hello World!\n'

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

下記 Dockerfile は問題なし。

FROM python:3.4
RUN pip install Flask==0.10.1
WORKDIR /app
COPY app /app
CMD ["python", "identidock.py"]

コンテナのビルド&起動も問題なし。

$ docker build -t identidock .
$ docker run -d -p 5000:5000 identidock

curlコマンドによる動作確認は・・・Windows環境なのでcurlがないのと、コンテナ内へホスト環境からアクセスするには上記Pythonコードの「host=0.0.0.0」をコンテナに割り当てられたIPアドレスに書き換える必要(?)があるようなので・・・せっかくなのでcurlがインストールされたDockerイメージを使って動作確認してみる。

$ docker run --rm -it --entrypoint /bin/sh byrnedo/alpine-curl
/ # curl http://172.17.0.3:5000/
Hello World!

IPアドレス「172.17.0.3」は、identidockイメージから起動されたコンテナのアドレス。本来ならば –link 等を使ってもっとエレガントにやるのだろうが・・・。続いて、アプリケーションサーバ uWSGI を使うためのDockerfile。

FROM python:3.4
RUN pip install Flask==0.10.1 uWSGI==2.0.8
WORKDIR /app
COPY app /app
CMD ["uwsgi", "--http", "0.0.0.0:9090", "--wsgi-file", "/app/identidock.py", \
     "--callable", "app", "--stats", "0.0.0.0:9191"]

uWSGIのコンパイル(?)エラーが出力されて、イメージのビルドに失敗する。。Pythonのバージョンを変えたり、FlaskやuWSGIのバージョン指定を外したりしても改善しなかったので、結局UbuntuをベースにPython3.6をインストールするやり方にしてみた。

FROM ubuntu:18.04
RUN apt-get update -y && apt-get install -yq python3-pip python3-dev wget && \
    wget https://bootstrap.pypa.io/get-pip.py && python3.6 get-pip.py && \
    pip install Flask uWSGI
WORKDIR /app
COPY app /app
CMD ["uwsgi", "--http", "0.0.0.0:9090", "--wsgi-file", "/app/identidock.py", "--callable", "app"]

CMD行も”–stats”あたりでエラーが出てコンテナが起動できなかったので、パラメタを削除して起動するようにした。

Angular7 in Docker

Docker for Windows(Docker Community Edition for Windows)をWindows 10 PCへインストールしたので、試しにAngular7をコンテナ内でビルドし、実行してみます。


1.Dockerfile を書いてみる

Node.jsがインストール済みのイメージファイルをベースにして、Angular CLIのインストールと新規プロジェクトの作成をすればよいので・・・こんな感じかな?

FROM node:10-alpine
WORKDIR /work
RUN npm install -g @angular/cli@7.0.3
RUN ng new --defaults angular7
WORKDIR /work/angular7
CMD ng serve

2.ビルド&実行

作った Dockerfile を使って ng new まで実行したイメージファイルを作り、実行してみる。

$ docker build -t angular7 .
$ docker run -p 4200:4200 --name angular7 angular7

上手くいったようだが・・・http://locahost:4200 を叩いてもアクセスできず。結局、いろいろ試したところ、DockerfileのCMD行を

CMD ng serve --host `hostname`

へ変更して再度ビルド&実行すると、ホストPC上のブラウザからは http://127.0.0.1:4200 でアクセスできることが判明。Docker for Windows 固有の問題なのだろうか・・・?とりあえず動いたから良しとしよう^^;


3.Yarn 発見

ふと気づいたのだが・・・

$ docker exec -i -t angular7 /bin/sh
/work/angular7 # yarn -v
1.10.1
/work/angular7 # which yarn
/usr/local/bin/yarn

Ultra Fastを謳う^^; Yarnコマンド(現時点の最新Stable版)が/usr/local/binにインストールされていた。早速、Dockerfileを書き換えてみる。

FROM node:10-alpine
WORKDIR /work
RUN yarn global add @angular/cli@7.0.3
RUN ng new --defaults --skipInstall=true angular7
WORKDIR /work/angular7
RUN yarn
CMD ng serve --host `hostname`

こんな使い方では速度を体感することもなく・・・動いたので良しとしよう^^;;

Angular7プロジェクトをFirebaseでホスティングしてみる

Angular7プロジェクトをFirebaseのHostingへデプロイして公開してみる。


1.Node.js&npmインストール

Node.js 本家 から推奨版(現時点では8.12.0 LTS)をダウンロードしてインストールする。


2.Angular CLIインストール

現時点の最新版(7.0.3)をインストールする。旧バージョンがインストール済みの場合はuninstallおよびcache verifyも実行する。

$ npm uninstall -g @angular/cli
$ npm cache verify
$ npm install -g @angular/cli@7.0.3

3.Firebase CLIインストール

Firebase CLIをインストールの上、Firebaseへログインする。

$ npm install -g firebase-tools
$ firebase login

ブラウザが開くので、Firebaseを利用するGoogleアカウントを選択(またはログイン)する。成功すれば、

Waiting for authentication...

+  Success! Logged in as xxxxxxxxxx@gmail.com

のようにコンソールに表示される。


4.Firebaseプロジェクト作成

https://console.firebase.google.com にて、Hosting用のプロジェクトを作っておく(今回は「angular7」を作った)。


5.Angularプロジェクト作成、ビルド

まずはAngularプロジェクト「angular7」を作る。

$ ng new angular7
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? SCSS   [ http://sass-lang.com   ]
CREATE angular7/angular.json (3877 bytes)
CREATE angular7/package.json (1315 bytes)
...

次に、デプロイ用のファイル一式を「angular7/public」ディレクトリへ生成する。

$ cd angular7
$ ng build --prod --output-path=public

6.FirebaseのHostingへデプロイする

angular7ディレクトリをそのままFirebaseのプロジェクトとして初期化する。カレントディレクトリをangular7のままで、以下を実行する。

$ firebase init

質問に対しては以下のように回答した(生成済みのpublic/index.htmlを上書きしないよう注意)。「angular7-xxxxx」はFirebaseプロジェクト作成時に割り当てられたプロジェクトIDであり、「xxxxx」部分は作成の都度、固有の文字列が割り当てられる。

? Are you ready to proceed?
 -> Yes
? Which Firebase CLI features do you want to setup for this folder? Press Space to select features, then Enter to confirm your choices.
 -> Hosting: Configure and deploy Firebase Hosting sites
? Select a default Firebase project for this directory:
 -> angular7-xxxxx (angular7)
? What do you want to use as your public directory?
 -> public
? Configure as a single-page app (rewrite all urls to /index.html)?
 -> No
? File public/index.html already exists. Overwrite?
 -> No

そしてデプロイを実行する。

$ firebase deploy
=== Deploying to 'angular7-xxxxx'...

i  deploying hosting
i  hosting[angular7-xxxxx]: beginning deploy...
i  hosting[angular7-xxxxx]: found 8 files in public
+  hosting[angular7-xxxxx]: file upload complete
i  hosting[angular7-xxxxx]: finalizing version...
+  hosting[angular7-xxxxx]: version finalized
i  hosting[angular7-xxxxx]: releasing new version...
+  hosting[angular7-xxxxx]: release complete

+  Deploy complete!

Project Console: https://console.firebase.google.com/project/angular7-xxxxx/overview
Hosting URL: https://angular7-xxxxx.firebaseapp.com

7.動作確認

Firebaseへデプロイ時に表示されたHosting URLへアクセスすると・・・

Welcome to angular7!

無事表示されただろうか。

Angular Material 7 で Virtual Scrolling を試してみる

Angular6.1で遊んでいたらAngular7がリリースされたので、Virtual Scrollingでちょっと遊んでみる。


1.Angular CLIのアップデート

まずは・・・先日Angular CLIを6.2.5へアップデートしたばかりだが、Angular CLIも7.0.2がリリースされていたのでアップデートする。

$ npm uninstall -g @angular/cli
$ npm cache verify
$ npm install -g @angular/cli@7.0.2

念のためバージョンを確認する。

$ ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 7.0.2
Node: 8.12.0
OS: win32 x64
Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.10.2
@angular-devkit/core         7.0.2
@angular-devkit/schematics   7.0.2
@schematics/angular          7.0.2
@schematics/update           0.10.2
rxjs                         6.3.3
typescript                   3.1.3

2.Angular7でプロジェクトを新規作成する

Angular CLI 7.X で ng new を実行すると、routingの使用やスタイルシートのフォーマットについて聞かれる。

$ ng new virtualscrolling
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? SCSS   [ http://sass-lang.com   ]
CREATE virtualscrolling/angular.json (3949 bytes)
CREATE virtualscrolling/package.json (1323 bytes)
CREATE virtualscrolling/README.md (1033 bytes)
...

プロジェクトができたところで、Angularのバージョンを確認してみる。

$ cd virtualscrolling
$ ng version
...
Angular CLI: 7.0.2
Node: 8.12.0
OS: win32 x64
Angular: 7.0.0
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router
...

無事 Angular 7.0.0 がインストールされた。


3.Angular Materialインストール

次は Angular Material 7.X をインストールする。

$ npm install --save @angular/material @angular/cdk @angular/animations
...
+ @angular/animations@7.0.0
+ @angular/material@7.0.0
+ @angular/cdk@7.0.0
...

インストールが終わったら、公式サイトの「Getting started | Angular Material」に従い、

Step 2: Configure animations
Step 4: Include a theme

を作成したプロジェクトに適用しておく(詳細は割愛)。他の手順は必要に応じて後でやる。さて、ここまでで一旦動かしてみる。

$ ng serve --open

問題なく「Welcome to virtualscrolling!」が表示される。


3.データの準備

とりあえず、そこそこの量のデータを・・・ということで、app.component.ts の codes 配列に、Unicode文字を0x0000 ~ 0x9999の約4万文字用意する。

import {Component, OnInit} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'virtualscrolling';
  codes = [];

  ngOnInit() {
    for (let i = 0x0000; i < 0x9999; i++) {
      this.codes.push({
        code: '0x' + ('0000' + i.toString(16).toLowerCase()).substr(-4),
        char: String.fromCharCode(i)
      });
    }
  }
}

4.Virtual Scrolling

まずは、app.component.html を以下のようにして、約4万文字をそのままループで表示してみる。

<div *ngFor="let i of codes">
  {{i.code}} - {{i.char}}
</div>

表示にかかる時間を計測すると、約9秒程度。
次に、Virtual Scrollingを試してみる。app.module.ts へ ScrollDispatchModule を追加して、

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ScrollDispatchModule } from '@angular/cdk/scrolling';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    ScrollDispatchModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.scss を(本家のサンプルをそのままに)以下とし、

.example-viewport {
  height: 200px;
  width: 200px;
  border: 1px solid black;
}

.example-item {
  height: 50px;
}

app.component.html を(こちらも本家のサンプルを参考に)以下とする。

<cdk-virtual-scroll-viewport itemSize="50" class="example-viewport">
  <div *cdkVirtualFor="let i of codes" class="example-item">{{i.code}} - {{i.char}}</div>
</cdk-virtual-scroll-viewport>

表示されるまでの時間は1秒以下(厳密には計測してないが、そのほとんどはAngularの初期化にかかる時間だろう)。DOMの状態をDevToolsで確認してみると、

<cdk-virtual-scroll-viewport _ngcontent-c0="" class="example-viewport cdk-virtual-scroll-viewport cdk-virtual-scroll-orientation-vertical" itemsize="50" ng-reflect-item-size="50">
  <div class="cdk-virtual-scroll-content-wrapper" style="transform: translateY(896950px);">
    <!--bindings={
      "ng-reflect-cdk-virtual-for-of": "[object Object],[object Object"
    }-->
    <div _ngcontent-c0="" class="example-item">0x4613 - 䘓</div>
    <div _ngcontent-c0="" class="example-item">0x4614 - 䘔</div>
    <div _ngcontent-c0="" class="example-item">0x4615 - 䘕</div>
    <div _ngcontent-c0="" class="example-item">0x4616 - 䘖</div>
    <div _ngcontent-c0="" class="example-item">0x4617 - 䘗</div>
    <div _ngcontent-c0="" class="example-item">0x4618 - 䘘</div>
    <div _ngcontent-c0="" class="example-item">0x4619 - 䘙</div>
    <div _ngcontent-c0="" class="example-item">0x461a - 䘚</div>
    <div _ngcontent-c0="" class="example-item">0x461b - 䘛</div>
    <div _ngcontent-c0="" class="example-item">0x461c - 䘜</div>
    <div _ngcontent-c0="" class="example-item">0x461d - 䘝</div>
  </div>
  <div class="cdk-virtual-scroll-spacer" style="transform: scaleY(1.96605e+06);"></div>
</cdk-virtual-scroll-viewport>

スクロールバーの操作に応じて、div.cdk-virtual-scroll-content-wrapper の内容が一定量を超えないよう動的に変化する動きをしていた。また、スクロールバーを正しく表示させるために、div.cdk-virtual-scroll-content-wrapper の translateY と、div.cdk-virtual-scroll-spacer の scaleY を変化させて表示行の上下に巨大な空白を作り出していた・・・なるほど、そういう仕組みなのね!

Angular6.1でHello World

Angular6もそろそろ安定してきただろう・・・ということで、インストール済みのnode等のバージョンアップとAngular6を軽く動作確認してみる。


1.Node.js バージョンアップ

まずは現状確認。

$ node -v
v6.11.0
$ npm -v
3.10.10

Angularの公式サイトでは「Node.js は 8.x 以上、npm は 5.x 以上」と書かれているのでバージョンアップが必要だ。で、簡単にバージョンアップするためのツール「n」なんてものもあるようだが・・・日々の使用頻度は少なく、そもそもインストールしたことすら忘れそうなのでNode.js本家から最新版(8.12.0 LTS)をダウンロードして普通に手動インストールする。

$ node -v
v8.12.0
$ npm -v
6.4.1

2.Angular CLI バージョンアップ

こちらも現状確認から。

$ ng -v

    _                      _                 ____ _     ___
   / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
  / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
 / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
/_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
               |___/

Angular CLI: 1.7.4
Node: 8.12.0
OS: win32 x64
Angular:
...

Angular CLI 6が出ているようなので、こちらもバージョンアップする。本家のドキュメント どおりに下記のコマンドを実行する。

$ npm uninstall -g @angular/cli
$ npm cache verify
$ npm install -g @angular/cli@latest

バージョンを確認してみると・・・無事バージョン6がインストールされたようだ。

$ ng -v

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 6.2.5
Node: 8.12.0
OS: win32 x64
Angular:
...

3.Angular6.1でHello World

Hello Worldするだけならコードを書くまでもなく・・・

$ ng new helloworld
$ cd helloworld
$ ng serve --open

デフォルトのブラウザ(例えばChrome)に「Welcome to helloworld!」と(一応「helloworld」と)表示される^^;
ちょっと手抜きすぎるので、本当にAngular6.1になっているのか確認してみる。app.component.tsでtitleにバージョンを埋め込んでみる。

import {Component, VERSION} from '@angular/core';

~略~
export class AppComponent {
  title = `helloworld (${VERSION.full})`;
}

「Welcome to helloworld (6.1.10)!」と表示され、Angular6.1.10であることが分かった。ng -vでも分るようだ。

$ ng -v

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 6.2.5
Node: 8.12.0
OS: win32 x64
Angular: 6.1.10
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

4.JSONデータの表示

以下のJSONデータ(message.json)をHTTP通信で取得して、画面に表示してみる。ファイルは「src/assets/message.json」に格納する。

{ "message": "Hello Angular World!" }

HttpClientModuleを使うので、app.module.tsのimportsに一行追加する。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Angularの入門としては適切ではなさそうだが・・・AsyncPipeを使って通信結果をテンプレート(app.component.html)に埋め込んでみる。まずは、app.component.ts に messageApi 変数と、constructor()を追加する。

import {Component, VERSION} from '@angular/core';
import {HttpClient} from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = `helloworld (${VERSION.full})`;
  messageApi = null;

  constructor(private http: HttpClient) {
    this.messageApi = this.http.get('assets/message.json');
  }
}

最後にテンプレートに以下を記述し、「3.Angular6.1でHello World」と同様に「ng serve –open」を実行する(実行済みなら、勝手に画面が更新される)。

{{ (messageApi | async)?.message }}

これで「GET http://localhost:4200/assets/message.json」が実行されてレスポンスのJSONデータがパースされ、JSONデータのmessageプロパティが画面に表示される。