Angularの開発環境セットアップ とコンテナビルドとK8sデプロイ

## はじめに

Single Page Applicationのフレームワーク Angular をコンテナで実行するまでを勉強したので、備忘録として残しておく。


## 1. node.jsのバージョン管理ツール nvm のセットアップ

macOSで、node.jsのバージョンを切り替えられるように、nvmをインストールして、その上でnode.jsを動作させ、Angularのコマンドを利用する。nvmのセットアップは、参考リンクを読めば解るので、補足はしない。

* 参考リンク https://github.com/nvm-sh/nvm


## 2. 最新バージョンのnode.jsをインストール

バージョンの指定無しでインストールを実行すれば、その時点の最新バージョンのnodeがインストールされる。

~~~shell: Terminal
$ nvm install
$ node --version
~~~


## 3. TypsScript コマンドのインストール

TypeScriptのスニペットのテストを実施するために、TypeScript から JavaScript への変換コマンド tsc をインストールする。TypeScriptをブラウザ上で実行するときは、JavaScriptへ変換されたコードが実行される。TypeScriptからnode.jsのモジュールを利用するなどもあるので、TypeScriptで書いたコードが、JavaScriptに変換されたときの内容が解るtscコマンドは重要なデバックツールになる。

~~~shell: Terminal
$ npm install -g typescript
~~~

* 参考URL https://www.typescriptlang.org/docs/handbook/typescript-tooling-in-5-minutes.html


## 4. Angular のコマンドをインストール

Angularのコマンドをインストールする。このコマンドは、Angularのアプリケーション開発の足がかりとなるコードを生成する。また、JavaScriptへ変換するビルドを実施することができる。

~~~shell: Terminal
$ npm install -g @angular/cli
~~~

Angularの ngコマンドのオプションなどは、参考URLで解説されているので、ブックマークしておくと重宝する。

* 参考URL https://angular.io/cli


## 5. Angular のサンプルアプリを作成してみる

Anglularでアプリケーションを開発するときは、最初に、最小コードやディレクトリ構造をngコマンドで生成してから、足していくのが良い。次のコマンドは、SPAのコード TestAppの足場となるコードを生成してくれる。

~~~shell: Terminal
$ ng new TestApp
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? SCSS   [ https://sass-lang.com/documentation/syntax#scss                ]
<中略>
✔ Packages installed successfully.
    Successfully initialized git.
~~~

以下はアプリケーションのディレクトリTestApp 以下に生成されたファイル群だ。アプリケーションのコードは、以下に付け足していく形で、開発を推進することができる。 

~~~shell: Terminal
$ tree TestApp/
TestApp/
├── README.md
├── angular.json
├── karma.conf.js
├── package.json
├── src
│   ├── app
│   │   ├── app.component.html
│   │   ├── app.component.scss
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts
│   ├── assets
│   ├── environments
│   │   ├── environment.prod.ts
│   │   └── environment.ts
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.scss
│   └── test.ts
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json

4 directories, 20 files
~~~

参考URL: 
* https://angular.io/tutorial/toh-pt1
* https://angular.io/start


## 6. Angular のテスト実行

上記のコマンド実行で、必要なコードを生成してくれるので、ディレクトリを移動して、'ng serve'を実行すると、HTTPサーバーが起動して、Angularのコードを、ブラウザ上で実行できる準備が整う。

~~~shell: Terminal
$ cd TestApp
$ ng serve
~~~

ターミナルにアクセスするべきURLは表示されるので、curlコマンドやブラウザを使って、動作や画面を確認することができる。

参考URL https://angular.io/cli/serve


## 7. コンテナのビルド準備

コンテナ化する場合でも 'ng serve' をWebサーバーとして利用することも出来なくはないが、本番用ではないため、脆弱性やパフォーマンスの懸念がある。そこで、Nginxのコンテンツとして、AngularのSPAをサーブする。そのためのDockerfileが以下だ。ここでのポイントは、ビルドとNginxのコンフィグの消去である。

~~~file:Dockerfile
# Stage 1 ビルドステージ
FROM node:latest as node
WORKDIR /app
COPY ./TestApp /app
RUN npm install
RUN npm run build --prod  # TypeScriptからJavaScriptへのビルド

# Stage 2 Nginxとの統合
FROM nginx:alpine
RUN rm /etc/nginx/conf.d/default.conf  # コンフィグの消去
COPY --from=node /app/dist/test-app /usr/share/nginx/html
~~~

ビルドでは、TypeScriptのソースコードから、ブラウザで実行するためのJavaScriptのコードを生成する。生成されたJavaScriptのコートは人が読むためのコードではなく、ブラウザで実行するためのコードなので、名称が変更されるなどが実施される。

Nginxとの統合では、Kubernetesにデプロイすることを想定して、TLSのための証明書、コンフィグファイルを、それぞれ、Secret, ConfigMapで外部から与えられるようにするために、default.confを消去しておく。そして、conf.d 以下を外部のボリュームから提供できるようにする。

ビルドステージで生成されたコードは、ディレクトリ dist/test-appに作成されるので、そのディレクトリ以下を、Nginxのドキュメントルート以下に、コピーする。


## 8. コンテナのビルドとローカルテスト

ここからは、Dokcerコマンドの使い方になる。必要となるオプションとパラメーターの意味は以下になる。

~~~shell: Terminal
export TAG=1.0
docker build -t <レジストリURL>/<ユーザーID>/<コンテナイメージ名>:$TAG .
~~~

筆者のローカルのラボ環境に、コンテナレジストリでCNCF の Harborを立てている場合は、タグを初めから設定しておくと便利だ。

~~~shell: Terminal
export TAG=1.0
docker build -t harbor.labo.local/tkr/spa:$TAG .
~~~

ビルドが完了したら、実行してアクセステストを実施する。同様にオプションは以下である。

~~~shell: Terminal
docker run --rm --name spa -p <公開ポート>:<コンテナポート> -v <ホストの絶対パス>:<コンテナ上のマウント位置> <レジストリURL>/<ユーザーID>/<コンテナイメージ名>:$TAG
~~~

環境依存なので、参考程度に残しておく。 「-ｐ」では、ポート番号はHTTPとHTTPSの両方のポートを開く。「-v」では、TLSのためのサーバー証明書と、Nginxのconf.d以下を、外部から与える。

~~~shell: Terminal
docker run --rm --name spa -p 5080:5080 -p 5443:5443 -v `pwd`/certs:/certs -v `pwd`/nginx:/etc/nginx/conf.d  harbor.labo.local/tkr/spa:$TAG
~~~


## 9. レジストリへ登録

Kubernetesでコンテナを実行するためには、コンテナをレジストリに登録しなければならない。以下は、レジストリに登録するには、アカウントを保有しており、そのアカウントでログインしていなければならない。この例では、コンテナレジストリ Harbor にログインしておき、登録するときのコマンド例だ。

~~~shell: Terminal
docker login -u tkr harbor.labo.local
docker push harbor.labo.local/tkr/spa:$TAG
~~~


## 10. Kubernetesへのデプロイ

アプリケーション専用の名前空間を作成しておき、公開用IPアドレスからDNSを登録するために、ロードバランサーをデプロイする。

~~~shell: Terminal
# 名前空間の作成
kubectl create ns spa

# ロードバランサーの設定、VIPの獲得
kubectl apply -f yamls/service.yaml -n spa
~~~

作業の前提として、以下の３点がある。特に、サーバー証明書のドメイン名が、DNS登録名と一致している昼用があるので、面倒だけど、外せない。

* コンテナはレジストリへ登録しておく。
* プライベートCAでサーバー証明書を作成しておく。
* プライベートDNSにドメイン名を登録しておく。

TLS用のサーバー証明書は、Secret spa.tlsに保存、そして、 Nginxのコンフィルは、Configmap nginx-configへ保存する。

~~~shell: Terminal
# サーバー証明書をデプロイ
kubectl create secret tls spa.tls \
  --cert=certs/tls.crt \
  --key=certs/tls.key \
  -n spa

# Nginxの設定ファイルをデプロイ
kubectl create configmap nginx-config --from-file=nginx -n spa
~~~

最後にKubernetesへデプロイする。

~~~shell: Terminal
# コンテナのデプロイ、ロードバランサーの設定
kubectl apply -k yamls -n spa
~~~

これで、`kubectl get po -n spa` や `kubectl get svc -n spa` で確認すれば良い。


## 11. プロジェクトのファイル構成

コンテナやKubernetesのYAMLなどを一箇所に集約して、プロジェクト用のディレクトリを作成しておくとと便利だ。nginx と yamlsの部分の内容は、後述する。

~~~
$ tree angular-apl
angular-apl
├── Dockerfile
├── README.md
├── TestApp
│   ├── README.md
│  <以下省略 前述に同じため>
│  
├── certs
│   ├── tls.crt
│   └── tls.key
├── nginx
│   ├── default.conf
│   └── ssl-server.conf
└── yamls
    ├── deployment.yaml
    ├── kustomization.yaml
    └── service.yaml
~~~


## 12. Nginxのコンフィルファイル

'/etc/nginx/conf.d' に配置する HTTPの設定ファイルである。 OpenShift環境へのデプロイも考慮して、特権ポートを避ける設定にする。

~~~file: default.conf
server {
    listen       5080;
    server_name  spa.labo.local;
    location / {        
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
~~~

こちらは、TLSで暗号化するWebサーバーの設定である。証明書と鍵は/certsの下にマウントする想定だ。

~~~file: ssl-server.conf 
server {
    listen 5443 ssl http2;
    #listen [::]:5443 ssl http2;
    server_name         spa.labo.local;
    root                /usr/share/nginx/html;
    ssl_certificate     /certs/tls.crt;
    ssl_certificate_key /certs/tls.key;
    ssl_session_timeout 1d;
    ssl_session_cache   shared:SharedNixCraftSSL:10m; 
    ssl_session_tickets off;
    ssl_protocols       TLSv1.3;
    ssl_prefer_server_ciphers off;
}
~~~


## 13. Kubernetes の YAMLファイル

ロードバランサーのIPアドレスは、アサインされるので、先にデプロイしておいて、IPアドレスをDNSへ登録するのが良いと思う。クラウドやKubernetesクラスタの構成により変わるので、参考程度にみて欲しい。

~~~file: service.yaml
apiVersion: v1
kind: Service
metadata:
  name: spa
spec:
  selector:
    app: spa
    group: spa-group
  ports:
  - name: https
    protocol: TCP
    port: 443
    targetPort: 5443
  - name: http
    protocol: TCP
    port: 80
    targetPort: 5080
  type: LoadBalancer
~~~

予めレジストリへ登録しておいたコンテナ harbor.labo.local/tkr/spa:1.0 を指定して、
ConfigMapとSecretをボリュームとしてマウントする。

~~~file: deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spa
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spa
      group: spa-group
  template:
    metadata:
      labels:
        app: spa
        group: spa-group
    spec:
      #imagePullSecrets:
      #- name: regcred
      containers:
      - name: spa
        image: harbor.labo.local/tkr/spa:1.0
        imagePullPolicy: Always
        volumeMounts:
        - name: tls
          mountPath: /certs
        - name: config
          mountPath: /etc/nginx/conf.d
      volumes:
      - name: tls
        secret:
          secretName: spa.tls
      - name: config
        configMap:
          name: nginx-config
~~~

さらにYAMLファイルが増える場合は、適用の順序が保証されるので、作成しておくと便利だ。

~~~file: kustomization.yaml
resources:
- service.yaml
- deployment.yaml
~~~

以上