Cloud Foundry에서 DEA와 Warden이 통신하여 응용 포트 감청 완료

Cloud Foundry v2 버전에서 DEA는 사용자 응용 프로그램이 실행하는 제어 모듈이고 응용의 진정한 실행은 Warden에 의존한다.더 구체적으로 말하면 DEA가 클라우드 컨트롤러의 요청을 받은 것이다.DEA Warden 서버에 요청 보내기;warden 서버에서 warden container를 만들고 사용자를droplet 등 환경에 적용하여 설정합니다.DEA는 Warden 서버에 응용 프로그램 시작 요청을 보냅니다.마지막으로 Warden container에서 시작 스크립트 시작 응용 프로그램을 실행합니다.
본고는 DEA가 Warden과 어떻게 상호작용을 하는지를 구체적으로 기술하여 최종 사용자의 응용이 특정한 포트를 성공적으로 연결하고 사용자 응용이 대외적으로 서비스를 제공할 수 있도록 한다.
DEA는 응용 프로그램을 시작할 때 주로 다음과 같은 부분을 한다:promisedroplet, promise_container, 그중 이 두 부분을 병발하여 완성;promise_extract_droplet, promise_exec_hook_script(“before_start”), promise_스타트 등.코드는 다음과 같습니다.
        [
          promise_droplet,
          promise_container
        ].each(&:run).each(&:resolve)

        [
          promise_extract_droplet,
          promise_exec_hook_script('before_start'),
          promise_start
        ].each(&:resolve)

promise_droplet:
이 부분에서 DEA가 주로 하는 일은droplet을 본기로 다운로드하는 것이다.droplet을 통해uri, 그 중에서 기본적인 경로는/config/dea입니다.yml에서 basedir:/tmp/dea_ng, 따라서 최종적으로 DEA에서 다운로드한droplet은 DEA 구성 요소가 있는 숙박 호스트에 저장됩니다.
 
promise_container:
이 부분의 작업은 주로 Warden container를 만들고 응용 프로그램의 실행에 적합한 환경을 제공할 수 있습니다.promise_container의 원본 코드는 다음과 같습니다.
def promise_container
      Promise.new do |p|
        bind_mounts = [{'src_path' => droplet.droplet_dirname, 'dst_path' => droplet.droplet_dirname}]
        with_network = true
        container.create_container(
          bind_mounts: bind_mounts + config['bind_mounts'],
          limit_cpu: config['instance']['cpu_limit_shares'],
          byte: disk_limit_in_bytes,
          inode: config.instance_disk_inode_limit,
          limit_memory: memory_limit_in_bytes,
          setup_network: with_network)
        attributes['warden_handle'] = container.handle
        promise_setup_def create_container(params)
    [:bind_mounts, :limit_cpu, :byte, :inode, :limit_memory, :setup_network].each do |param|
      raise ArgumentError, "expecting #{param.to_s} parameter to create container" if params[param].nil?
    end

    with_em do
      new_container_with_bind_mounts(params[:bind_mounts])
      limit_cpu(params[:limit_cpu])
      limit_disk(byte: params[:byte], inode: params[:inode])
      limit_memory(params[:limit_memory])
      setup_network if params[:setup_network]
    end
  endenvironment.resolve
        p.deliver
      end
    end

전송된 파라미터를 볼 수 있습니다: bindmounts: 호스트 파일 디렉터리의 경로를 완성하고 mount를container 내부로 이동합니다.       limit_cpu:container의 CPU 자원 분배를 제한하는 데 사용합니다.byte: 디스크 한도액;innode: 디스크의 innode 제한 limit메모리 한도액;       setup_네트워크: 네트워크의 설정 항목입니다.
 
여기서 setupnetwork는true로 설정되어 있습니다.
container에서.create_container의 방법 구현에는 다음과 같은 방법이 있습니다.
def create_container(params)
    [:bind_mounts, :limit_cpu, :byte, :inode, :limit_memory, :setup_network].each do |param|
      raise ArgumentError, "expecting #{param.to_s} parameter to createdef create_container(params)
    [:bind_mounts, :limit_cpu, :byte, :inode, :limit_memory, :setup_network].each do |param|
      raise ArgumentError, "expecting #{param.to_s} parameter to create container" if params[param].nil?
    end

    with_em do
      new_container_with_bind_mounts(params[:bind_mounts])
      limit_cpu(params[:limit_cpu])
      limit_disk(byte: params[:byte], inode: params[:inode])
      limit_memory(params[:limit_memory])
      setup_network if params[:setup_network]
    end
  end container" if params[param].nil?
    end

    with_em do
      new_container_with_bind_mounts(params[:bind_mounts])
      limit_cpu(params[:limit_cpu])
      limit_disk(byte: params[:byte], inode: params[:inode])
      limit_memory(params[:limit_memory])
      setup_network if params[:setup_network]
    end
  end

 
setup에 주목해주세요네트워크 방법은 다음과 같습니다.
def setup_network
    request = ::Warden::Protocol::NetInRequest.new(handle: handle)
    response = call(:app, request)
    network_ports['host_port'] = response.host_port
    network_ports['container_port'] = response.container_port

    request = ::Warden::Protocol::NetInRequest.new(handle: handle)
    response = call(:app, request)
    network_ports['console_host_port'] = response.host_port
    network_ports['console_container_port'] = response.container_port
  end

 
코드에서 볼 수 있듯이 setupNetwork에서는 주로 두 차례의 NetIn 작업이 완료되었습니다.NetIn 작업에 대해 설명해야 할 것은, 완성된 작업은host 호스트의 포트를container 내부의 포트에 비추는 것이다.다시 말하면 hostip:port1이container에 비추기ip:port2, 그러니까 만약에 container가container에 있다면ip에서 감청하는 것은 포트port2이다.host기계 외부의 요청이host기계에 접근하고 포트가port1일 때host의 내부 핵 네트워크 창고는container의port2 포트에 요청을 전송하는데 그 중에서 사용하는 프로토콜은 DNAT 프로토콜이다.
따라서 상기 코드에서 두 차례의 NetIn 조작을 실현했다. 즉,container의 두 포트를host숙 호스트에 비추고 첫 번째 포트는container에서 응용하는 정상적인 점용 포트에 사용되며, 두 번째 포트는console 기능을 응용하는 데 사용된다.container도 두 번째 포트를 분배했지만 이후의 응용 프로그램 시작 등에서 이consoleport도 사용하지 않은 것을 보면 클라우드 Foundry는 여기에 인터페이스를 미리 남겼을 뿐 실제로 이용하지 못했다.
이상은 주로 NetIn의 기능을 설명했고 다음은 NetIn 작업에 들어가는 원본 코드 실현이다.NetIn의 원본 코드는 Warden 서버의 일부분입니다.그 중에서 DEA 프로세스가 Warden을 통과합니다.sock과 Warden 서버가 통신을 맺고 DEA 프로세스에서 NetIn 요청을 Warden 서버에 보내고 Warden 서버가 최종적으로 이 요청을 처리합니다.
 
이제 Warden의 범주에 들어가 Warden이 요청을 어떻게 받아들이고 포트의 맵을 실현하는지 연구한다.warden/lib/warden/server에 있습니다.rb에서 대부분의 코드는 Warden 서버의 운행을 완성하기 위해run에서!방법에서 Warden 서버가 유닉스 domain 서버를 시작하는 것을 볼 수 있습니다. 코드는 다음과 같습니다.
server = ::EM.start_unix_domain_server(unix_domain_path, ClientConnection)

 
즉, Warden 서버는 전체 unix domain 서버를 통해 DEA 프로세스에서 보내온 Warden container에 대한 일련의 요청을 수신하고 클라이언트 연결 클래스에서 이 부분의 요청을 어떻게 처리하는지에 대한 방법을 정의합니다.
유닉스 domain 서버에서 ClientConnection 클래스가receive데이터(data) 방법으로 요청을 수신할 수 있습니다. 코드는 다음과 같습니다.
      def receive_data(data)
        @buffer << data
        @buffer.each_request do |request|
          begin
            receive_request(request)
          rescue => e
            close_connection_after_writing
            logger.warn("Disconnected client after error")
            logger.log_exception(e)
          end
        end
      end

 
코드에서 볼 수 있듯이 버퍼에 요청이 있을 때receive요청을 추출하기 위해 Request (Request) 방법을 사용합니다.다시 Receiverequest (request) 방법에서 프로세스 (request) 를 통해 요청을 처리하는 것을 볼 수 있습니다.
 
이어서 진정으로 처리를 요청하는 부분에 들어가면 프로세스(request)의 실현이 이루어진다.
def process(request)
        case request
        when Protocol::PingRequest
          response = request.create_response
          send_response(response)

        when Protocol::ListRequest
          response = request.create_response
          response.handles = Server.container_klass.registry.keys.map(&:to_s)
          send_response(response)

        when Protocol::EchoRequest
          response = request.create_response
          response.message = request.message
          send_response(response)

        when Protocol::CreateRequest
          container = Server.container_klass.new
          container.register_connection(self)
          response = container.dispatch(request)
          send_response(response)

        else
          if request.respond_to?(:handle)
            container = find_container(request.handle)
            process_container_request(request, container)
          else
            raise WardenError.new("Unknown request: #{request.class.name.split("::").last}")
          end
        end
      rescue WardenError => e
        send_error(e)
      rescue => e
        logger.log_exception(e)
        send_error(e)
      end

 
이를 통해 알 수 있듯이 warden 서버에서 요청 유형은 Ping Request,List Request,Echo Request,Create Request와 다른 요청으로 간단하게 나눌 수 있다. 예를 들어 NetIn 요청은 다른 요청 중의 하나에 속하고 프로그램이case 문장 블록에 들어가는else 지점에 들어간다. 즉:
	if request.respond_to?(:handle)
            container = find_container(request.handle)
            process_container_request(request, container)
          else
            raise WardenError.new("Unknown request: #{request.class.name.split("::").last}")
          end

        
코드가 뚜렷하게 보입니다. Warden 서버는 먼저handle을 통해 어떤 Warden container에게 요청을 보내고 프로세스를 호출하는지 찾습니다container_request 방법.프로세싱에 들어가기container_Request(request,container) 방법을 보면 다음과 같다. 요청 형식이 StopRequest 및StreamRequest가 아니라case 문장 블록의else 지점에 들어가 코드를 실행한다.
 	response = container.dispatch(request)
         send_response(response)

 
보시다시피 컨테이너를 호출했습니다.dispatch (request) 방법이response로 되돌아왔습니다.
 
다음은 Warden/lib/warden/container/base에 들어갑니다.rb 파일에서 이 파일의 디스패치 방법은 주로 Warden 서버가 요청을 받고 미리 처리한 후에 구체적인 요청을 어떻게 나누어 집행하는지 코드는 다음과 같다.
def dispatch(request, &blk)
        klass_name = request.class.name.split("::").last
        klass_name = klass_name.gsub(/Request$/, "")
        klass_name = klass_name.gsub(/(.)([A-Z])/) { |m| "#{m[0]}_#{m[1]}" }
        klass_name = klass_name.downcase

        response = request.create_response

        t1 = Time.now

        before_method = "before_%s" % klass_name
        hook(before_method, request, response)
        emit(before_method.to_sym)

        around_method = "around_%s" % klass_name
        hook(around_method, request, response) do
          do_method = "do_%s" % klass_name
          send(do_method, request, response, &blk)
        end

        after_method = "after_%s" % klass_name
        emit(after_method.to_sym)
        hook(after_method, request, response)

        t2 = Time.ndef dispatch(request, &blk)
        klass_name = request.class.name.split("::").last
        klass_name = klass_name.gsub(/Request$/, "")
        klass_name = klass_name.gsub(/(.)([A-Z])/) { |m| "#{m[0]}_#{m[1]}" }
        klass_name = klass_name.downcase

        response = request.create_response

        t1 = Time.now

        before_method = "before_%s" % klass_name
        hook(before_method, request, response)
        emit(before_method.to_sym)

        around_method = "around_%s" % klass_name
        hook(around_method, request, response) do
          do_method = "do_%s" % klass_name
          send(do_method, request, response, &blk)
        end

        after_method = "after_%s" % klass_name
        emit(after_method.to_sym)
        hook(after_method, request, response)

        t2 = Time.now

        logger.info("%s (took %.6f)" % [klass_name, t2 - t1],
                    :request => request.to_hash,
                    :response => response.to_hash)

        response
      endow

        logger.info("%s (took %.6f)" % [klass_name, t2 - t1],
                    :request => request.to_hash,
                    :response => response.to_hash)

        response
      end

 
먼저 요청의 유형 이름을 추출합니다. NetIn 요청이라면 요청의 유형 이름은 net 입니다.in, 이후 구축 방법 명도method, 즉donet_in, 이어서send(do method,request,response,&blk) 방법을 통해do 에 매개 변수를 보냅니다.net_인 방법.
 
지금은 Warden/lib/warden/container/features/net에 들어갑니다.rb 파일에서 donet_in 방법은 다음과 같습니다.
def do_net_in(request, response)
          if request.host_port.nil?
            host_port = self.class.port_pool.acquire

            # Use same port on the container side as the host side if unspecified
            container_port = request.container_port || host_port

            # Port may be re-used after this container has been destroyed
            @resources["ports"] << host_port
            @acquired["ports"] << host_port
          else
            host_port = request.host_port
            container_port = request.container_port || host_port
          end

          _net_in(host_port, container_port)

          @resources["net_in"] ||= []
          @resources["net_in"] << [host_port, container_port]

          response.host_port      = host_port
          response.container_port = container_port
        rescue WardenError
          self.class.port_pool.release(host_port) unless request.host_port
          raise
        end

요청 포트가 지정되지 않으면 코드host 를 사용합니다.port = self.class.port_pool.acquire에서 포트 번호를 가져옵니다. 기본적으로container 포트 번호와host 포트 번호가 일치합니다. 이 두 포트 번호가 있으면 실행 코드net_in(host port, container port)은 다음과 같이 포트 매핑을 구현합니다.
def _net_in(host_port, container_port)
          sh File.join(container_path, "net.sh"), "in", :env => {
            "HOST_PORT"      => host_port,
            "CONTAINER_PORT" => container_port,
          }
        end

 
용기 내부의 넷을 사용한 것이 뚜렷하게 보인다.sh 스크립트로 포트 맵을 만듭니다.이제 Warden/root/linux/skeleton/net으로 들어갑니다.sh 스크립트, 인자가 in인 실행 부분에 들어가기:
"in")
    if [ -z "${HOST_PORT:-}" ]; then
      echo "Please specify HOST_PORT..." 1>&2
      exit 1
    fi
    if [ -z "${CONTAINER_PORT:-}" ]; then
      echo "Please specify CONTAINER_PORT..." 1>&2
      exit 1
    fi
    iptables -t nat -A ${nat_instance_chain} \
      --protocol tcp \
      --destination "${external_ip}" \
      --destination-port "${HOST_PORT}" \
      --jump DNAT \
      --to-destination "${network_container_ip}:${CONTAINER_PORT}"
    ;;

 
이를 통해 알 수 있듯이 이 스크립트의 기능은 host 호스트에 DNAT 규칙을 만들어서 host 호스트에 있는 모든 HOST 를PORT 포트의 네트워크 요청이 모두 network로 전달됩니다.container_ip:CONTAINER_PORT에서 대상 주소 IP 변환이 완료되었습니다.
 
promise_extract_droplet:
이 부분은 주로 container가 스크립트를 실행하도록 하고 container 용기는host 호스트에 있는droplet 파일을 container 내부로 압축하여 코드 내용은 다음과 같다.
def promise_extract_droplet
      Promise.new do |p|
        script = "cd /home/vcap/ && tar zxf #{droplet.droplet_path}"
        container.run_script(:app, script)
        p.deliver
      end
    end

promise_exec_hook_script('before_start'):
이 부분에서 주로 완성된 기능은 용기 운행명을beforestart의 스크립트입니다. 이 부분의 설정은 기본적으로 비어 있습니다.
promise_start:
이 부분에서 주로 완성된 것은 응용의 시작이다.컨테이너를 만들 때 반환된 포트 번호는 DEA에 의해 저장됩니다.최종적으로 DEA가 프로그램을 시작할 때, DEA는 포트 번호를 매개 변수로 프로그램의 시작 스크립트에 전달하기 때문에, 프로그램이 시작될 때 이미 설정된 포트, 즉 포트 감청을 자동으로 감청합니다.
 
 
작성자 정보:
손홍량, DAOCLOUD 소프트웨어 엔지니어.2년 동안 클라우드 컴퓨팅에서 주로 PaaS 분야의 관련 지식과 기술을 연구했습니다.경량급 가상화 용기의 기술이 PaaS 분야에 깊은 영향을 미치고 미래의 PaaS 기술의 방향까지 결정할 것이라고 굳게 믿는다.
전재는 출처를 밝혀 주십시오.
이 글은 내 자신의 이해에서 더 많이 나온 것이기 때문에 틀림없이 일부 부분에 부족함과 착오가 존재할 것이다.본고는 Warden 포트에 비치는 사람들을 접촉하는 데 도움이 되었으면 합니다. 이 분야에 관심이 있고 더 좋은 생각과 건의가 있으면 저에게 연락 주십시오.
내 메일박스:[email protected]
연꽃

좋은 웹페이지 즐겨찾기