2020系統綜合實踐4 Docker專題實踐 - 負載均衡、JavaWeb、Hadoop大數據集群


使用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

wait

Tomcat+SpringBoot啟動

spring

運行結果

偷個懶就不寫前端了(主要是因為不會)。

測試


使用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。

參見官方關於Java版本的說明

博主一開始用的就是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.shstop-dfs.sh文件,添加下列參數:

HDFS_DATANODE_USER=root
HADOOP_SECURE_DN_USER=hdfs
HDFS_NAMENODE_USER=root
HDFS_SECONDARYNAMENODE_USER=root

對於start-yarn.shstop-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

hosts

測試ssh

在master上,ssh相應主機即可訪問目標主機,exit命令可以斷開ssh連接。

ssh slave01 # 第一次使用需要輸入yes
ssh slave02

ssh

修改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 # 查看服務狀態

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

output


其他問題及解決方法

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.shstart-yarn.sh替代即可。

bash: hadoop: command not found

Hadoop環境變量沒配好,參考上面Java的配法,換一下路徑即可。

沒有slaves配置文件

Hadoop 3.xslaves更名為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 配置詳解 | 菜鳥教程

從JavaWeb的角度認識Nginx - 左羽 - 博客園

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

ubuntu更新軟件源報錯: Certificate verification failed: The certificate is NOT trusted._運維_sinat_38800908的博客-CSDN博客

bash: hadoop: command not found_大數據_tucailing的專欄-CSDN博客

為什么hadoop沒有slaves配置文件?_大數據_若閑小閣-CSDN博客

ERROR: but there is no HDFS_NAMENODE_USER defined. Aborting operation. Starting datanodes_運維_liangsw-CSDN博客

vim的幾種模式_運維_Mr.yang-CSDN博客

Hadoop Java Versions - Hadoop - Apache Software Foundation

Apache Hadoop 3.2.1 – Hadoop: Setting up a Single Node Cluster.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM