使用Docker-compose實現Tomcat+Nginx負載均衡
nginx反向代理原理
使用nginx代理tomcat
項目結構
├── docker-compose.yml
├── nginx
│ └── default.conf
├── tomcat1
│ └── index.html
├── tomcat2
│ └── index.html
└── tomcat3
└── index.html
為了區分是哪一個服務器,為3只tomcat分別創建了3個index.html。
nginx配置文件
upstream tomcats {
server ctc1:8080; # 主機名:端口號
server ctc2:8080; # tomcat默認端口號8080
server ctc3:8080; # 默認使用輪詢策略
}
server {
listen 2420;
server_name localhost;
location / {
proxy_pass http://tomcats; # 請求轉向tomcats
}
}
docker-compose.yml
由於設置了upstream
,nginx的啟動依賴於tomcat,可以用depends_on
實現。
version: "3.8"
services:
nginx:
image: nginx
container_name: cngx
ports:
- 80:2420
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf # 掛載配置文件
depends_on:
- tomcat01
- tomcat02
- tomcat03
tomcat01:
image: tomcat
container_name: ctc1
volumes:
- ./tomcat1:/usr/local/tomcat/webapps/ROOT # 掛載web目錄
tomcat02:
image: tomcat
container_name: ctc2
volumes:
- ./tomcat2:/usr/local/tomcat/webapps/ROOT
tomcat03:
image: tomcat
container_name: ctc3
volumes:
- ./tomcat3:/usr/local/tomcat/webapps/ROOT
負載均衡測試
輪詢策略
權重策略
修改nginx配置文件:
upstream tomcats {
server ctc1:8080 weight=1;
server ctc2:8080 weight=2;
server ctc3:8080 weight=3;
}
...
重啟nginx容器:
docker restart cngx
使用Docker-compose部署javaweb運行環境
在上面的基礎上,添加數據庫容器,然后把javaweb打包成WAR,放在tomcat的webapps目錄下即可。
項目結構
├── docker-compose.yml
├── mariadb
│ ├── Dockerfile
│ ├── docker-entrypoint.sh
│ └── schema.sql
├── nginx
│ └── default.conf
└── tomcat
├── Dockerfile
├── ROOT.war
└── wait-for-it.sh
MariaDB
Dockerfile for mariadb
FROM mariadb
LABEL author=qyanzh
# mysql的工作位置
ENV WORK_PATH /usr/local/
# 定義會被容器自動執行的目錄
ENV AUTO_RUN_DIR /docker-entrypoint-initdb.d
#復制schema.sql到/usr/local
COPY schema.sql /usr/local/
#把要執行的shell文件放到/docker-entrypoint-initdb.d/目錄下,容器會自動執行這個shell
COPY docker-entrypoint.sh $AUTO_RUN_DIR/
#給執行文件增加可執行權限
RUN chmod a+x $AUTO_RUN_DIR/docker-entrypoint.sh
docker-entrypoint.sh
#!/bin/bash
mysql -uroot -p1234 << EOF
source /usr/local/schema.sql;
schema.sql
create database db_example;
-- Creates the new database
create user 'springuser' @'%' identified by 'ThePassword';
-- Creates the user
grant all on db_example.* to 'springuser' @'%';
-- Gives all privileges to the new user on the newly created database
Tomcat
javaweb應用的啟動依賴於mariadb,僅用depends_on
是無法實現的,因為對於docker來說,容器只要啟動了,就算是啟動成功。而對於javaweb應用來說,數據庫需要能訪問才算啟動成功。容器啟動和數據庫能訪問有一個時間差,所以就會導致javaweb應用啟動失敗。而應用啟動失敗不會導致tomcat退出,所以restart
也就沒用了。在StackOverflow上找到了一種方法是用health_check
,也沒用。最后終於找到了一個腳本wait-for-it.sh,可以很好的解決這個問題。
Dockerfile for tomcat
FROM tomcat
LABEL author=qyanzh
COPY ./wait-for-it.sh /usr/local/tomcat/bin/
RUN chmod +x /usr/local/tomcat/bin/wait-for-it.sh
CMD ["wait-for-it.sh", "cdb:3306", "--", "catalina.sh", "run"]
docker-compose.yml
version: "3.8"
services:
mariadb:
image: my-mariadb
container_name: cdb
restart: always
build:
context: ./mariadb
environment:
MYSQL_ROOT_PASSWORD: "1234"
nginx:
image: nginx
container_name: cngx
ports:
- 80:2420
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- tomcat01
- tomcat02
- tomcat03
tomcat01:
image: tomcat
container_name: ctc1
build:
context: ./tomcat
volumes:
- ./tomcat:/usr/local/tomcat/webapps
depends_on:
- mariadb
restart: always
tomcat02:
image: tomcat
container_name: ctc2
build:
context: ./tomcat
volumes:
- ./tomcat:/usr/local/tomcat/webapps
depends_on:
- mariadb
restart: always
tomcat03:
image: tomcat
container_name: ctc3
build:
context: ./tomcat
volumes:
- ./tomcat:/usr/local/tomcat/webapps
depends_on:
- mariadb
restart: always
JavaWeb
之前學過一點點SpringBoot,就用SpringBoot做了。有一個問題就是SpringBoot是自帶Tomcat的,不能直接使用,需要做一些修改把自帶的Tomcat去除掉,限於篇幅就不詳細展開了,可以參考這里。SpringBoot的教程也放在了參考里。下面貼一些主要代碼。
application.properties
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://cdb:3306/db_example # 主機名修改為容器名
spring.datasource.username=springuser
spring.datasource.password=ThePassword
MainController.java
package com.example.accessingdatamysql;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping(path = "/demo")
public class MainController {
@Autowired
private UserRepository userRepository;
@PostMapping(path = "/add")
public @ResponseBody
String addNewUser(@RequestParam String name
, @RequestParam String email) {
User n = new User();
n.setName(name);
n.setEmail(email);
n = userRepository.save(n);
return "Saved. Your id is " + n.getId();
}
@PostMapping(path = "/update")
public @ResponseBody
String updateUser(@RequestParam Integer id,
@RequestParam String name
, @RequestParam String email) {
User n = userRepository.findById(id).orElse(null);
if (n != null) {
n.setName(name);
n.setEmail(email);
userRepository.save(n);
return "Updated successfully.";
} else {
return "Id doesn't exist";
}
}
@PostMapping(path = "/delete")
public @ResponseBody
String deleteUser(@RequestParam Integer id) {
if (userRepository.findById(id).orElse(null) != null) {
userRepository.deleteById(id);
return "Deleted.";
} else {
return "Id doesn't exist";
}
}
@GetMapping(path = "/all")
public @ResponseBody
Iterable<User> getAllUsers() {
return userRepository.findAll();
}
@GetMapping(path = "/who")
public @ResponseBody
String getPort(HttpServletRequest request) {
return "hello from " + request.getLocalAddr();
}
}
運行測試
wait-for-it
Tomcat+SpringBoot啟動
運行結果
偷個懶就不寫前端了(主要是因為不會)。
使用Docker搭建大數據集群環境
目錄結構
├── Dockerfile
├── build
│ └── hadoop-3.2.1.tar.gz
├── config
│ ├── hadoop-env.sh
│ ├── hdfs-site.xml
│ ├── mapred-site.xml
│ └── yarn-site.xml
└── sources.list
環境
-
Docker容器環境:
Ubuntu 20.04 LTS
-
JDK版本:
openjdk 1.8.0_252
-
Hadoop版本:
Hadoop 3.2.1
環境搭建
ubuntu容器
建議換清華源(方便起見,把每行https
改成http
)。
FROM ubuntu
LABEL author=qyanzh
COPY ./sources.list /etc/apt/sources.list
docker build -t ubuntu .
docker run -it --name ubuntu ubuntu
現在就進入了ubuntu容器中。
ubuntu容器初始化
安裝vim與ssh
apt-get update
apt-get install vim # 用於修改配置文件
apt-get install ssh # 分布式hadoop通過ssh連接
/etc/init.d/ssh start # 開啟sshd服務器
vim ~/.bashrc # 在文件末尾添加上一行的內容,實現ssd開機自啟
配置sshd 實現無密碼登錄
ssh-keygen -t rsa # 一直按回車即可
cd ~/.ssh
cat id_rsa.pub >> authorized_keys # 教程中的dsa應為拼寫錯誤
安裝JDK
注意:apt-get默認安裝的是JDK11,而Hadoop 3.x目前僅支持Java 8,2.x支持Java 7/8。
博主一開始用的就是JDK11,途中遇到各種問題,最后才發現是JDK版本太高的原因,換到JDK8后各種問題都迎刃而解。哎說多了都是淚,以后一定要先查好版本依賴,避免這種低級錯誤。
apt-cache search jdk
apt-get install openjdk-8-jdk
vim ~/.bashrc # 在文件末尾添加以下兩行,配置Java環境變量:
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/
export PATH=$PATH:$JAVA_HOME/bin
source ~/.bashrc # 使.bashrc生效
做完以上步驟之后另開一個終端存個檔:
docker ps # 查看當前容器id
docker commit 容器ID ubuntu/jdk8 # 存檔
docker run -it -v /home/qyanzh/h4/h4_3/build:/root/build --name ubuntu-jdk8 ubuntu/jdk8
# 掛載是為了讀取Hadoop安裝文件,也可以直接用docker cp命令
安裝Hadoop
老師給的教程中的Hadoop版本為2.7.1,與本文使用的3.2.1配置略有不同;各版本的Hadoop可以在官網下載(binary→清華鏡像)。
把下載好的Hadoop放在掛載的目錄下並安裝:
cd /root/build
tar -zxvf hadoop-3.2.1.tar.gz -C /usr/local
cd /usr/local/hadoop-3.2.1
./bin/hadoop version # 驗證安裝
輸出如下:
Hadoop 3.2.1
Source code repository https://gitbox.apache.org/repos/asf/hadoop.git -r b3cbbb467e22ea829b3808f4b7b01d07e0bf3842
Compiled by rohithsharmaks on 2019-09-10T15:56Z
Compiled with protoc 2.5.0
From source with checksum 776eaf9eee9c0ffc370bcbc1888737
This command was run using /usr/local/hadoop-3.2.1/share/hadoop/common/hadoop-common-3.2.1.jar
配置Hadoop集群
先進入配置文件存放目錄:
cd /usr/local/hadoop-3.2.1/etc/hadoop
hadoop_env.sh
vim hadoop-env.sh
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/ # 在任意位置添加
core-site.xml
vim core-site.xml
<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl" ?>
<configuration>
<property>
<name>hadoop.tmp.dir</name>
<value>file:/usr/local/hadoop/tmp</value>
<description>Abase for other temporary directories.</description>
</property>
<property>
<name>fs.defaultFS</name>
<value>hdfs://master:9000</value>
</property>
</configuration>
hdfs-site.xml
vim hdfs-site.xml
<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl" ?>
<configuration>
<property>
<name>dfs.namenode.name.dir</name>
<value>file:/usr/local/hadoop/namenode_dir</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>file:/usr/local/hadoop/datanode_dir</value>
</property>
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
</configuration>
mapred-site.xml
相比舊版多了三個配置,都設為hadoop目錄即可。
vim mapred-site.xml
<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl" ?>
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>yarn.app.mapreduce.am.env</name>
<value>HADOOP_MAPRED_HOME=/usr/local/hadoop-3.2.1</value>
</property>
<property>
<name>mapreduce.map.env</name>
<value>HADOOP_MAPRED_HOME=/usr/local/hadoop-3.2.1</value>
</property>
<property>
<name>mapreduce.reduce.env</name>
<value>HADOOP_MAPRED_HOME=/usr/local/hadoop-3.2.1</value>
</property>
</configuration>
yarn-site.xml
vim yarn-site.xml
<?xml version="1.0" ?>
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>master</value>
</property>
</configuration>
修改腳本
先進入腳本文件存放目錄:
cd /usr/local/hadoop-3.2.1/sbin
對於start-dfs.sh
和stop-dfs.sh
文件,添加下列參數:
HDFS_DATANODE_USER=root
HADOOP_SECURE_DN_USER=hdfs
HDFS_NAMENODE_USER=root
HDFS_SECONDARYNAMENODE_USER=root
對於start-yarn.sh
和stop-yarn.sh
,添加下列參數:
YARN_RESOURCEMANAGER_USER=root
HADOOP_SECURE_DN_USER=yarn
YARN_NODEMANAGER_USER=root
注意放在合適的位置,比如function{}
之后。
運行Hadoop集群
運行主機
做完以上步驟之后另開一個終端存個檔,然后開啟三個終端,分別運行集群中的主機:
docker commit 容器ID ubuntu/hadoop # 存檔
# 第一個終端
docker run -it -h master --name master ubuntu/hadoop
# 第二個終端
docker run -it -h slave01 --name slave01 ubuntu/hadoop
# 第三個終端
docker run -it -h slave02 --name slave02 ubuntu/hadoop
修改/etc/hosts
為三台主機配置對方的地址信息,他們才能找到彼此。三個終端分別修改/etc/hosts:
vim /etc/hosts # 查看各終端的IP並修改
內容均修改成如下形式即可,多余的可以刪去:
x.x.x.x master
x.x.x.x slave01
x.x.x.x slave02
測試ssh
在master上,ssh相應主機即可訪問目標主機,exit命令可以斷開ssh連接。
ssh slave01 # 第一次使用需要輸入yes
ssh slave02
修改workers
在master上:
vim /usr/local/hadoop-3.2.1/etc/hadoop/workers # 舊版為slaves
將localhost
修改為:
slave01
slave02
測試Hadoop集群
在master上:
cd /usr/local/hadoop-3.2.1
bin/hdfs namenode -format # 格式化文件系統
sbin/start-dfs.sh # 開啟NameNode和DataNode服務
bin/hdfs dfs -mkdir /user # 建立HDFS文件夾,也可以放到下面示例程序中進行
bin/hdfs dfs -mkdir /user/root
bin/hdfs dfs -mkdir input
bin/hdfs dfs -put etc/hadoop/*.xml input # 將xml復制到input下,作為示例程序輸入
sbin/start-yarn.sh # 開啟ResourceManager和NodeManager服務
jps # 查看服務狀態
運行Hadoop示例程序
在master上:
bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.2.1.jar grep input output 'dfs[a-z.]+' # 運行示例
bin/hdfs dfs -get output output # 獲取輸出結果
cat output/* # 查看輸出結果
sbin/stop-all.sh # 停止所有服務
運行結果:
1 dfsadmin
1 dfs.replication
1 dfs.namenode.name.dir
1 dfs.datanode.data.dir
其他問題及解決方法
Certificate verification failed
換源后apt-get update提示:
Certificate verification failed: The certificate is NOT trusted. The certificate issuer is unknown. Could not handshake: Error in the certificate verification.
把源中的https
全換成http
即可。
exec: "/usr/src/app/entrypoint.sh": permission denied"
復制腳本到容器需要給權限:
RUN chmod +x xxx.sh
Error: JAVA_HOME is not set and could not be found
export
指令僅在本次登錄有效,寫入.bashrc
中即可自動執行。參見安裝JDK
This script is Deprecated. Instead use start-dfs.sh and start-yarn.sh
start-all.sh
在新版本已不推薦使用,用start-dfs.sh
和start-yarn.sh
替代即可。
bash: hadoop: command not found
Hadoop環境變量沒配好,參考上面Java的配法,換一下路徑即可。
沒有slaves配置文件
Hadoop 3.x
后slaves
更名為workers
。參見修改workers
ERROR: but there is no HDFS_NAMENODE_USER defined
新版需要在腳本中配置一些變量,參見修改腳本
Could not find or load main class org.apache.hadoop.mapreduce.v2.app.MRAppMaster
新版需要在mapred-site.xml
中多配置3個屬性,參見mapred-site.xml
其他Hadoop錯誤的定位方法
運行腳本后四個服務(參見 測試Hadoop集群 的截圖)如果有沒跑起來的,可以通過查看hadoop3.2.1/logs
下對應服務的日志來定位錯誤。一般看最下面一個的Caused By
就行。
vim的一些操作
- Esc 進入正常模式
- Insert 進入插入模式
- 正常模式下輸入
:
進入命令模式 - dd 剪切整行
- :%d 刪除所有行
- :q! 不保存退出
- :wq! 保存退出
用時和心得
完整源碼已上傳至GitHub
作業內容用時約16小時,博客約3小時。心得:遇錯多看Log,不要瞎Debug。
參考
第4次實踐作業 - 作業 - 2017級系統綜合實踐 - 班級博客 - 博客園
Nginx服務器之負載均衡策略(6種) - 左羽 - 博客園
Docker下Nginx+Tomcat實現負載均衡_運維_菲宇運維-CSDN博客
Getting Started | Building a RESTful Web Service
Getting Started | Accessing data with MySQL
Getting Started | Converting a Spring Boot JAR Application to a WAR
SpringBoot去除內嵌tomcat_Java_樹欲靜而風不止-CSDN博客
SpringBoot項目的創建和jar、war方式的部署_Java_鐺鐺當的博客-CSDN博客
docker-compose 啟動依賴 wait-for-it.sh 實例_運維_金戈鐵馬-CSDN博客
vishnubob/wait-for-it: Pure bash script to test and wait on the availability of a TCP host and port
alpine - Docker-compose "exec: "/usr/src/app/entrypoint.sh": permission denied" - Stack Overflow
bash: hadoop: command not found_大數據_tucailing的專欄-CSDN博客
為什么hadoop沒有slaves配置文件?_大數據_若閑小閣-CSDN博客
Hadoop Java Versions - Hadoop - Apache Software Foundation
Apache Hadoop 3.2.1 – Hadoop: Setting up a Single Node Cluster.