解決問題

解決問題 SSL/TLS (7)

教學目標

主要解決如何透過 Windows Server 2012 R2 網站伺服器的 IIS 管理員產生適用於設定 Apache 網站伺服器的私密金鑰檔和數位憑證檔之問題。

重點概念

首先 Windows Server 2012 R2 網站伺服器的 IIS 管理員角色中主要能夠匯出 PKCS#7 和 PKCS#12 兩種類型的公鑰加密標準 (Public Key Cryptography Standards, PKCS) 格式檔案,所謂 PKCS#7 密碼訊息語法標準 (Cryptographic Message Syntax Standard) 主要規範以公開金鑰基礎設施 (Public Key Infrastructure,PKI) 所產生之簽章密文之格式,應用於數位憑證,其副檔名為 P7B,至於 PKCS#12 個人資訊交換標準 (Personal Information Exchange Syntax Standard) 主要定義了包含私鑰與公鑰憑證的檔案格式,應用於數位憑證,但是包括私鑰資訊,其副檔名為 PFX。

接著在企業中我們主要會透過Windows Server 2012 R2 網站伺服器的 IIS 管理員建立憑證要求,我們必須指定驗證的必要資訊,主要有一般名稱、組織、組織單位、縣市位置、省份 國家 (地區),其中一般名稱必須設定正確的對外的網址名稱,才能夠正確驗證數位憑證是否有效。並且選擇密碼編譯服務提供者內容為「Microsoft RSA SChannel Cryptographic Provider」,位元長度設定「2048」,就能夠產生憑證要求檔,副檔名為 TXT。此時我們將會傳送憑證要求檔給企業內部的憑證管理中心網站產生數位憑證檔,也就是 P7B 格式的檔案,但是僅有數位憑證檔是無法設定 Apache 網站伺服器的 HTTPS 安全連線,所以我們需要先將數位憑證檔匯入至 Windows Server 2012 R2 網站伺服器之後,我們才能夠透過 IIS 管理員角色匯出包括私密金鑰檔案和數位憑證檔,也就是 PFX 格式檔案。

再來如何將 PFX 檔案轉成私密金鑰檔和數位憑證檔,若是在 Windows 伺服器平台,則能夠 Apache Lounge 中的 openssl 工具進行轉換,請注意在執行 openssl 工具之前必須先安裝Microsoft Visual C++ 可轉散發套件,否則會出現「無法啟動程式,因為您的電腦遺失MSVCP140.dll」的錯誤訊息。此外當我們透過以下指令產生私密金鑰檔時除了輸入當時產生 PFX 所設定的密碼之外,還需輸入暗號 (passphrase),但是若我們需要將私密金鑰檔設定 Apache 網站伺服器的 HTTPS 安全連線設定,則必須再透過以下指令將私密金鑰檔中的暗號 (passphrase) 移除。

1
2
3
> openssl pkcs12 -in server.pfx -clcerts -nokeys -out server.crt
> openssl pkcs12 -in server.pfx -nocerts -nodes -out server_encrypted.key
> openssl rsa -in server_encrypted.key -out server.key

最後當我們產生出私密金鑰檔 (server.key) 和數位憑證檔 (server.crt) 設定至 Apache網站伺服器的設定檔 (httpd-ssl.conf) 中,並且將 Apache 網站伺服器的設定檔 (httpd.conf) 中的 「LoadModule ssl_module modules/mod_ssl.so」 和 「Include conf/extra/httpd-ssl.conf」 前面的註解取消,以及也將的註解也取消掉,重新啟動 Apache 網站伺服器,就能夠完成 Apache 網站伺服器的 HTTPS 安全連線。

相關資源

解決問題 Active Directory (2)

教學目標

主要解決將 Linux 伺服器加入 Windows Active Directory 網域中的問題。

重點概念

首先我們要如何將 Linux 伺服器加入 Windows Active Directory 網域中,主要有三個階段,分別為:

  1. 前置作業。
  2. 加入網域。
  3. 測試驗證。

前置作業

接著前置作業又可分別為三個步驟,分別為:

  1. 安裝必要套件。
  2. 設定 Kerberos 客戶端。
  3. 取得 Kerberos 票據。

安裝必要套件

一開始我們必須安裝八個主要的套件,主要有:

  1. realmd
  2. sssd
  3. oddjob
  4. oddjob-mkhomedir
  5. adcli
  6. samba-common
  7. krb5-workstation
  8. krb5-devel
1
$ sudo yum -y install realmd sssd oddjob oddjob-mkhomedir adcli samba-common krb5-workstation krb5-devel

設定 Kerberos 客戶端

當安裝完成必要套件之後,我們必須設定 Kerberos 客戶端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ sudo vi /etc/krb5.conf
[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log

[libdefaults]
dns_lookup_realm = false
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
rdns = false
default_realm = CORP.COM
default_ccache_name = KEYRING:persistent:%{uid}

[realms]
CORP.COM = {
kdc = AD.CORP.COM
admin_server = AD.CORP.COM
}

[domain_realm]
.corp.com = CORP.COM
corp.com = CORP.COM
:wq!

取得 Kerberos 票據

當完成 Kerberos 客戶端設定之後,我們就需要取得 Kerberos 票據

1
2
3
4
5
$ kinit -V  [網域管理者帳號]
Using default cache: persistent:0:0
Using principal: [網域管理者帳號]@CORP.COM
Password for [網域管理者帳號]@CORP.COM:
Authenticated to Kerberos v5

加入網域

再來加入網域又可分別為三個步驟,分別為:

  1. 設定工具。
  2. 探索網域。
  3. 加入網域。

設定工具

我們主要會透過 realmd 工具將 Linux 伺服器加入網域,因此我們必須先設定 realmd.conf 工具。

1
2
3
4
5
6
7
8
$ vi /etc/realmd.conf
[users]
default-home = /home/%U
default-shell = /bin/bash

[corp.com]
fully-qualified-names = no
:wq!

探索網域

當設定完成之後,我們能夠嘗試查看網域資訊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ realm discover --verbose CORP.COM
* Resolving: _ldap._tcp.corp.example.com
* Performing LDAP DSE lookup on: 192.168.202.5
* Successfully discovered: corp.example.com
corp.example.com
type: kerberos
realm-name: CORP.COM
domain-name: corp.example.com
configured: no
server-software: active-directory
client-software: sssd
required-package: oddjob
required-package: oddjob-mkhomedir
required-package: sssd
required-package: adcli
required-package: samba-common

加入網域

當查看網域資訊完成之後,我們就能夠透過網域管理者帳號加入 Windows Active Directory 網域。

1
$ realm join -U [網域管理者帳號] --computer-name [主機名稱] CORP.COM

測試驗證

最後測試驗證又可分別為三個步驟,分別為:

  1. 確認網域資訊
  2. 取得使用者資訊
  3. 登入使用者

確認網域資訊

我們主要透過 realmd 工具確認網域資訊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ realm list
CORP.COM
type: kerberos
realm-name: CORP.COM
domain-name: corp.com
configured: kerberos-member
server-software: active-directory
client-software: sssd
required-package: oddjob
required-package: oddjob-mkhomedir
required-package: sssd
required-package: adcli
required-package: samba-common-tools
login-formats: %U@corp.com
login-policy: allow-realm-logins

取得使用者資訊

當確認網域資訊完成之後,我們就能夠嘗試取得網域中的使用者資訊。

1
2
$ id CORP\\USER
uid=1747601109(USER@CORP.COM) gid=1747600513(domain users@CORP.COM) groups=1747600513(domain users@CORP.COM)

登入使用者

當確認取得使用者資訊之後,最後一步就是登入網域中的使用者,此時若能夠成功登入,並且建立使用者目錄,就已經完成將 Linux 伺服器加入 Windows Active Directory 網域中了。

1
2
3
$ su - CORP\\USER
Password:
Creating home directory for USER@CORP.COM.

總結我們僅需簡單三個階段的步驟就能夠將 Linux 伺服器加入 Windows Active Directory 網域中。

相關資源

解決問題 Convolutional Neural Networks (1)

教學目標

初步了解如何透過 AWS 雲端服務解決圖片識別分類應用的問題。

重點概念

首先人工智慧有許多應用皆是進行圖片識別為基礎,然而針對圖片分類的教學皆是以 MNIST 或 CIFAR-10 為主,其中資料皆是已經準備好,而我們僅需要透過一個函數就能夠把所有資料皆載入進行,所以相對簡單適合初學者進行概念的學習。但是若在實務上若我們要採用特定的圖片進行分類時,要如何從頭開始呢?此時我們就能夠透過 AWS 深度學習映像檔產生的雲端機器進行圖片識別,並且撰寫 Python 程式以 Keras 搭配 Tensorflow 後端引擎進行模型訓練,當模型訓練完成之後,我們就能夠針對任何一張圖片進行識別。

接著我們透過 MobaXterm 工具登入至雲端機器之後,主要有五個步驟,分別為:

  1. 啟動 TensorFlow 環境。
  2. 安裝更新套件與確認版本。
  3. 將檔案分類至資料夾中。
  4. 訓練圖片識別模型。
  5. 預測圖片識別分類。

啟動 TensorFlow 環境

1
$ source activate tensorflow_p36

安裝更新套與確認版本

1
2
3
4
5
6
7
8
9
10
$ pip install tensorflow
$ pip install keras
$ pip install opencv-python
$ pip install imutils
$ pip install sklearn
$ python
$ import tensorflow as tf
$ tf.__version__
$ import keras
$ keras.__version__

將檔案分類至資料夾中

1
2
3
4
5
6
7
8
9
10
11
from shutil import copyfile
import csv
import os.path
train_data = []
with open('/home/ubuntu/ai/train.csv') as csvfile:
readCSV = csv.reader(csvfile, delimiter=',')
for row in readCSV:
train_data.append(row)
print(row[0],row[1])
if os.path.isfile('/home/ubuntu/ai/test/' + row[0] + '.png') :
copyfile('/home/ubuntu/ai/test/' + row[0] + '.png', '/home/ubuntu/ai/train/' + row[1] + '/' + row[0] + '.png')

訓練圖片識別模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras import backend as K
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import Adam
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import img_to_array
from keras.utils import to_categorical
from imutils import paths
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import cv2
import os
import sys

matplotlib.use("Agg")
sys.path.append('..')

EPOCHS = 3
INIT_LR = 1e-3
BS = 32
CLASS_NUM = 62
norm_size = 64

class LeNet:
@staticmethod
def build(width, height, depth, classes):
model = Sequential()
inputShape = (height, width, depth)
if K.image_data_format() == "channels_first": #for tensorflow
inputShape = (depth, height, width)
model.add(Conv2D(20, (5, 5),padding="same",input_shape=inputShape))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Conv2D(50, (5, 5), padding="same"))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(Flatten())
model.add(Dense(500))
model.add(Activation("relu"))
model.add(Dense(classes))
model.add(Activation("softmax"))
return model

def load_data(path):
print("[資訊] 載入圖片...")
data = []
labels = []
imagePaths = sorted(list(paths.list_images(path)))
random.seed(42)
random.shuffle(imagePaths)
for imagePath in imagePaths:
image = cv2.imread(imagePath)
image = cv2.resize(image, (norm_size, norm_size))
image = img_to_array(image)
data.append(image)
label = int(imagePath.split(os.path.sep)[-2])
labels.append(label)
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)
labels = to_categorical(labels, num_classes=CLASS_NUM)
return data,labels

def train(aug,trainX,trainY,testX,testY):
model = LeNet.build(width=norm_size, height=norm_size, depth=3, classes=CLASS_NUM)
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])

print("[資訊] 訓練模型...")
H = model.fit_generator(aug.flow(trainX, trainY, batch_size=BS),
validation_data=(testX, testY), steps_per_epoch=len(trainX),
epochs=EPOCHS, verbose=1)

print("[資訊] 儲存模型...")
model.save('image_classification.model')

model = LeNet.build(width=norm_size, height=norm_size, depth=3, classes=CLASS_NUM)
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])

print("[資訊] 訓練模型...")
H = model.fit_generator(aug.flow(trainX, trainY, batch_size=BS),
validation_data=(testX, testY), steps_per_epoch=len(trainX),
epochs=EPOCHS, verbose=1)
print("[資訊] 儲存模型...")
model.save('image_classification.model')

train_file_path = '/home/ubuntu/ai/train/'
test_file_path = '/home/ubuntu/ai/validation/'
trainX,trainY = load_data(train_file_path)
testX,testY = load_data(test_file_path)
aug = ImageDataGenerator(rotation_range=30, width_shift_range=0.1,
height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,
horizontal_flip=True, fill_mode="nearest")
train(aug,trainX,trainY,testX,testY);

預測圖片識別分類

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from keras.preprocessing.image import img_to_array
from keras.models import load_model
import numpy as np
import imutils
import cv2
norm_size = 64
def predict():
model = load_model('image_classification.model')
predict_result=[]
for i in range(1,20000):
img_path = '/home/ubuntu/ai/'+str(i)+'.png'
image = cv2.imread(img_path)
print("[資訊] 預測圖片" + img_path)
image = cv2.resize(image, (norm_size, norm_size))
image = image.astype("float") / 255.0
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
result = model.predict(image)[0]
proba = np.max(result)
label = str(np.where(result==proba)[0])
label = "{}: {:.2f}%".format(label, proba * 100)
predict_result.append([i,label[1]])
import pandas as pd
df = pd.DataFrame(predict_result)
df.to_csv("/home/ubuntu/ai/result.csv")

predict()

再來本篇主要採用 LeNet 經典深度學習的卷積神經網路,早在 1998 年就被提出,主要用於手寫數字的識別,已經有完整的卷積神經網路的基本框架構,包括卷積、激活、池化和全連接等元件,但是在 1998 年之後,深度學習並沒有太多的突破,直到 2012 年資料量的快速成長、運算速度的提升,以及更多有關卷積神經網路,像是 AlexNet,才開啟深度學習的全新時代,進行快速發展的階段。

最後我們僅需要透過雲端服務,採用「p2.xlarge」等級的 EC2 雲端主機,就能夠在十五分鐘內透過上述五個步驟針對 64×64 的二萬張圖片以 LeNet 經典深度學習的卷積神經網路進行模型訓練,以及再針對二萬張圖片進行識別預測,輕鬆達到分類率為 97% 的結果。但若是要達到分類率為 99% 的精準度,我們就必須再花時間深入了解更多有關卷積神經網路的應用,像是 AlexNet、VGG、GoogLeNet、ResNet、SENet、…等。此外我們測試時建議可以先在本機電腦安裝 Anaconda 啟動 Jupyter Notebook 網站工具進行資料處理和模型建置與測試,待確認無誤之後,再部署至雲端主機進行快速的運算。

預測圖片識別結果

(註:此結果主要是將 Epoch 設為 50,但是實際測試的預測結果準確率還是 97%,僅供參考。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
Epoch 1/50
20000/20000 [==============================] - 687s 34ms/step - loss: 0.1871 - acc: 0.9121 - val_loss: 0.0693 - val_acc: 0.9674
Epoch 2/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0871 - acc: 0.9590 - val_loss: 0.0589 - val_acc: 0.9681
Epoch 3/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0699 - acc: 0.9671 - val_loss: 0.0504 - val_acc: 0.9738
Epoch 4/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0609 - acc: 0.9712 - val_loss: 0.0353 - val_acc: 0.9839
Epoch 5/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0545 - acc: 0.9743 - val_loss: 0.0328 - val_acc: 0.9842
Epoch 6/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0501 - acc: 0.9768 - val_loss: 0.0317 - val_acc: 0.9850
Epoch 7/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0461 - acc: 0.9786 - val_loss: 0.0304 - val_acc: 0.9849
Epoch 8/50
20000/20000 [==============================] - 679s 34ms/step - loss: 0.0436 - acc: 0.9798 - val_loss: 0.0288 - val_acc: 0.9866
Epoch 9/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0407 - acc: 0.9815 - val_loss: 0.0320 - val_acc: 0.9847
Epoch 10/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0392 - acc: 0.9823 - val_loss: 0.0327 - val_acc: 0.9843
Epoch 11/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0371 - acc: 0.9834 - val_loss: 0.0303 - val_acc: 0.9873
Epoch 12/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0351 - acc: 0.9842 - val_loss: 0.0253 - val_acc: 0.9888
Epoch 13/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0340 - acc: 0.9848 - val_loss: 0.0165 - val_acc: 0.9928
Epoch 14/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0327 - acc: 0.9855 - val_loss: 0.0258 - val_acc: 0.9886
Epoch 15/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0317 - acc: 0.9860 - val_loss: 0.0200 - val_acc: 0.9908
Epoch 16/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0305 - acc: 0.9869 - val_loss: 0.0184 - val_acc: 0.9921
Epoch 17/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0292 - acc: 0.9874 - val_loss: 0.0238 - val_acc: 0.9899
Epoch 18/50
20000/20000 [==============================] - 678s 34ms/step - loss: 0.0282 - acc: 0.9876 - val_loss: 0.0197 - val_acc: 0.9921
Epoch 19/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0274 - acc: 0.9882 - val_loss: 0.0130 - val_acc: 0.9950
Epoch 20/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0264 - acc: 0.9886 - val_loss: 0.0166 - val_acc: 0.9930
Epoch 21/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0263 - acc: 0.9888 - val_loss: 0.0166 - val_acc: 0.9937
Epoch 22/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0254 - acc: 0.9891 - val_loss: 0.0136 - val_acc: 0.9946
Epoch 23/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0246 - acc: 0.9896 - val_loss: 0.0124 - val_acc: 0.9950
Epoch 24/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0239 - acc: 0.9899 - val_loss: 0.0137 - val_acc: 0.9944
Epoch 25/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0237 - acc: 0.9901 - val_loss: 0.0130 - val_acc: 0.9947
Epoch 26/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0226 - acc: 0.9905 - val_loss: 0.0175 - val_acc: 0.9925
Epoch 27/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0223 - acc: 0.9908 - val_loss: 0.0104 - val_acc: 0.9962
Epoch 28/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0222 - acc: 0.9907 - val_loss: 0.0171 - val_acc: 0.9931
Epoch 29/50
20000/20000 [==============================] - 682s 34ms/step - loss: 0.0214 - acc: 0.9912 - val_loss: 0.0104 - val_acc: 0.9969
Epoch 30/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0212 - acc: 0.9912 - val_loss: 0.0100 - val_acc: 0.9958
Epoch 31/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0208 - acc: 0.9914 - val_loss: 0.0117 - val_acc: 0.9952
Epoch 32/50
20000/20000 [==============================] - 679s 34ms/step - loss: 0.0201 - acc: 0.9917 - val_loss: 0.0120 - val_acc: 0.9953
Epoch 33/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0204 - acc: 0.9916 - val_loss: 0.0149 - val_acc: 0.9946
Epoch 34/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0194 - acc: 0.9920 - val_loss: 0.0107 - val_acc: 0.9958
Epoch 35/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0192 - acc: 0.9920 - val_loss: 0.0089 - val_acc: 0.9964
Epoch 36/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0190 - acc: 0.9922 - val_loss: 0.0095 - val_acc: 0.9964
Epoch 37/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0187 - acc: 0.9924 - val_loss: 0.0100 - val_acc: 0.9961
Epoch 38/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0184 - acc: 0.9926 - val_loss: 0.0149 - val_acc: 0.9950
Epoch 39/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0181 - acc: 0.9928 - val_loss: 0.0104 - val_acc: 0.9958
Epoch 40/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0178 - acc: 0.9927 - val_loss: 0.0139 - val_acc: 0.9940
Epoch 41/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0179 - acc: 0.9927 - val_loss: 0.0093 - val_acc: 0.9959
Epoch 42/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0171 - acc: 0.9931 - val_loss: 0.0093 - val_acc: 0.9964
Epoch 43/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0171 - acc: 0.9932 - val_loss: 0.0127 - val_acc: 0.9946
Epoch 44/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0167 - acc: 0.9933 - val_loss: 0.0083 - val_acc: 0.9964
Epoch 45/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0170 - acc: 0.9932 - val_loss: 0.0081 - val_acc: 0.9971
Epoch 46/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0164 - acc: 0.9934 - val_loss: 0.0079 - val_acc: 0.9971
Epoch 47/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0159 - acc: 0.9936 - val_loss: 0.0095 - val_acc: 0.9967
Epoch 48/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0162 - acc: 0.9935 - val_loss: 0.0089 - val_acc: 0.9962
Epoch 49/50
20000/20000 [==============================] - 681s 34ms/step - loss: 0.0158 - acc: 0.9938 - val_loss: 0.0108 - val_acc: 0.9960
Epoch 50/50
20000/20000 [==============================] - 680s 34ms/step - loss: 0.0156 - acc: 0.9938 - val_loss: 0.0075 - val_acc: 0.9969

相關資源

解決問題 F5 (1)

教學目標

初步了解如何透過 AWS 雲端服務解決需要有個環境測試 F5 BIG-IP 產品相關設定問題。

重點概念

首先許多企業皆是採用 F5 BIG-IP 進行網站負載平衡的應用,此時若我們面臨需要有個環境測試 F5 BIG-IP 產品相關設定問題時,則能夠透過 AWS 雲端服務在半小時建立一套試用期三個月的版本,至於要如何申請試用的註冊碼,則可以透過官方網站進行申請,以利進行相關測試。

接著我們主要能夠先從 AWS 市集訂閱「F5 BIG-IP Virtual Edition - BETTER - (BYOL) 」方案,主要透過 AWS EC2 建立 F5 BIG-IP 的虛擬環境實體,一開始我們可以先在網站應用程式的 AWS VPC 中建立 F5 BIG-IP 即可,至於相關步驟請參考官方教學影片教學文件

再來我們建立完成 F5 BIG-IP 的虛擬環境實體之後,請先新增一個 「Elastic IP address」關聯 F5 BIG-IP 的虛擬環境實體,以利進行註冊碼線上驗證與更新。並且透過「MobaXterm 工具」登入至 F5 BIG-IP 的虛擬環境實體,請輸入以下指令編輯管理者密碼,請注意因為還未授權所以僅能執行以下指令。

1
2
# modify auth password admin
# save sys config

最後我們就能夠透過瀏覽器開啟「https://:8443」登入至 F5 BIG-IP 管理介面,以利進行進行註冊碼線上驗證與更新。當註冊碼線上驗證與更新完成之後,就能夠開始建立「Pool 」和「Virtual Server」的基本設定以利透過 F5 BIG-IP 連線至相同 VPC 中的網站應用程式,以利解決需要有個環境測試 F5 BIG-IP 產品相關設定的問題。

相關資源

解決問題 Performance (2)

教學目標

初步了解 JVM 中垃圾回收器的基本概念以利解決應用程式效能的問題。

重點概念

首先當我們啟動 Java 網站應用程式時,將會透過 Java 虛擬機器 (Java Virtual Machine,JVM) 執行程式,並且每個 Java 網站應用程式皆會執行在獨立的 JVM 中,並且在啟動 JVM 時皆會配置 Heap 記憶體的大小,也就優化入門調整 -Xms 和 -Xmx 參數值設定 Heap 記憶體的大小,若記憶體夠大且有效能考量,則建議兩個參數設定一樣,以及每個 JVM 中同時執行多執行緒,其中有個執行緒稱為 GC 執行緒,它主要會將不必要的物件,也就是將不使用的物件進行回收,並且進行清除。

記憶體大小相關參數 說明
-Xms 主要設定啟動 JVM 時使用 Heap 記憶體的大小。
-Xmx 主要設定在 JVM 中使用 Heap 記憶體的最大值。

接著在 JVM 的垃圾回收皆採用分代回收的演算法,所謂分代回收主要是基於物件不同的生命周期採取不同的回收方式,以利提高垃圾回收的效率。其中主要分為三個世代,分別為:

  1. 新生代:一開始物件會建立於新生代中,並且生命週期很短。每次新生代的垃圾回收之後,只有少數的物件存活,就是所謂 Minor GC,主要透過複製演算法進行回收。
  2. 老年代:當物件在新生代中經歷多次垃圾回收之後仍然存活,就會被放至老年代中,並且生命週期較長。每次老年代的垃圾回收,將會花費較長的時間,就是所謂 Major GC,主要透過「標記/清理」或「標記/整理」演算法進行回收,至於所謂 Full GC 則是指新生代和老年代同時進行回收。
  3. 持久代:主要儲存中繼資料,像是類別、方法、…等資訊,與垃圾回收機制沒有直接相關。

此外我們能夠透過 jconsole 工具中的記憶體頁籤中觀察三個世代的使用情況。其中又可再分為 Heap 記憶體和非 Heap 記憶體,所謂 Heap 記憶體主要是代表新生代和老年代,而非 Heap 記憶體主要是代表持久代。

垃圾回收名稱 說明
Minor GC 主要發生在新生代中,發生頻率高和清理速度快。
Major GC 主要發生在老年代中,發生頻率低和清理速度慢。
Full GC 主要同時發生在新生代和老年代中,發生頻率低和清理速度慢。

再來新生代又會再分為三個區域分別為一個 Eden 區和二個 Survivor 區,大部份會在 Eden 區被建立,當 Eden 區滿時,還存活的物件將會被複製至兩個 Survivor 區之中。當 Survivor 區滿時,此區的存活物件若不滿足晉升條件時就會被複製至另一個 Survivor 區。同時當物件每經歷一次 Minor GC,則年齡加 1,直到達晉升年齡門檻值之後,就會使放至老年代中,因此晉升年齡門檻值的大小直接影響物件在 新生代中停留的時間,主要是透過 MaxTenuringThreshold 參數進行設定,預設值為 15。

新生代空間大小相關參數 說明
-XX:NewRatio 主要設定新生代和老年代配置記憶體的比例。
-XX:SurvivorRatio 主要設定 Eden 區和 Survivor 區配置記憶體的比例。
-XX:NewSize 主要設定新生代配置記憶體的大小。

最後在 JVM 中則有許多不同類型的垃圾回收器,將能夠適用於不同的情境,其中常見的垃圾回收器,分別為:

  1. Serial:主要是單執行緒的垃圾回收器,適用新生代和老年代。
  2. ParNew:主要是多執行緒的垃圾回收器,適用新生代和老年代。
  3. Parallel Scavenge:主要以輸出量優先的垃圾回收器,適用新生代。
  4. Concurrent Mark Sweep:主要以最短回應停頓時間為目標的垃圾回收器,適用老年代。
  5. G1:主要是將 Heap 記憶體分為多個相等大小的區域,按照每個區域進行垃圾回收,特點為平行處理、分代收集,不會導致空間碎片,並且能夠設定停頓時間的上限,適用新生代和老年代。

相關資源