...

суббота, 9 февраля 2019 г.

[Из песочницы] Интеграция React Native и C++ для iOS и Android

Недавно мне предложили поработать над одним интересным проектом. Требовалось разработать мобильное приложение для американского стартапа на платформах iOS и Android с помощью React Native. Ключевой технической особенностью и фактором, который однозначно решил мое участие в проекте, стала задача интегрировать библиотеку, написанную на языке С++. Для меня это могло быть новым опытом и новым профессиональным испытанием.

Почему было необходимо интегрировать С++ библиотеку


Данное приложение было необходимо для двухфакторной аутентификации с помощью протоколов FIDO UAF и U2F, использующих биометрические данные, таких как Face ID и Touch ID, и аналогичных технологий для Android платформы. Клиент для аутентификации был уже готов. Это была библиотека, написанная на С++ и применяемая некоторыми другими клиентами помимо мобильного приложения. Так что от меня требовалось встроить ее аналогичным образом в мобильное приложение на React Native.

Как я это делал


Существует подход для интеграции С++ в React Native приложение от Facebook. Однако проблема в том, что он работает только для платформы iOS, и не понятно, что делать с Android в данном случае. Мне же хотелось решить проблему сразу для двух платформ.

Форк инструмента Djinni от Dropbox, который позволяет генерировать кросс-платформенные объявления типов. По сути он является простым мобильным приложением на React Native с настроенной связью с Djinni. Именно его я взял за основу.

Для удобства код приложения разбит на два git-репозитория. В первом хранится исходный код React Native приложения, а во втором – Djinni и необходимые зависимости.

Дальнейшие шаги


Сначала необходимо объявить интерфейс взаимодействия С++ и React Native кода. В Djinni это делается с помощью .idl файлов. Откроем файл react-native-cpp-support/idl/main.Djinni в проекте и ознакомимся с его структурой.

В проекте для нашего удобства уже объявлены некоторые типы данных JavaScript и биндинги для них. Таким образом, мы можем работать с типами String, Array, Map, Promise и другими без какого-либо дополнительного их описания.

В примере этот файл выглядит так:

DemoModule = interface +r {
  const EVENT_NAME: string = "DEMO_MODULE_EVENT";
  const STRING_CONSTANT: string = "STRING";
  const INT_CONSTANT: i32 = 13;
  const DOUBLE_CONSTANT: f64 = 13.123;
  const BOOL_CONSTANT: bool = false;

  testPromise(promise: JavascriptPromise);
  testCallback(callback: JavascriptCallback);
  testMap(map: JavascriptMap, promise: JavascriptPromise);
  testArray(array: JavascriptArray, callback: JavascriptCallback);
  testBool(value: bool, promise: JavascriptPromise);
  testPrimitives(i: i32, d: f64, callback: JavascriptCallback);
  testString(value: string, promise: JavascriptPromise);

  testEventWithArray(value: JavascriptArray);
  testEventWithMap(value: JavascriptMap);
}


После внесения изменений в файл интерфейсов необходимо перегенерировать Java/Objective-C/C++ интерфейсы. Это легко сделать запустив скрипт generate_wrappers.sh из папки react-native-cpp-support/idl/. Этот скрипт соберет все объявления из нашего idl файла и создаст соответствующие интерфейсы для них, это очень удобно.

В примере есть два интересующих нас С++ файла. Первый содержит описание, а второй реализацию простых С++ методов:

react-native-cpp/cpp/DemoModuleImpl.hpp
react-native-cpp/cpp/DemoModuleImpl.cpp

Рассмотрим код одного из методов в качестве примера:

void DemoModuleImpl::testString(const std::string &value, const std::shared_ptr<::JavascriptPromise> &promise) {
    promise->resolveObject(JavascriptObject::fromString("Success!"));
}


Обратите внимание, что результат возвращается не с помощью keyword return, а с помощью объекта JavaScriptPromise, переданного последним параметром, как и описано в idl файле.

Теперь стало понятно, как описывать необходимый код в С++. Но как взаимодействовать с этим в React Native приложении? Чтобы понять, достаточно открыть файл из папки react-native-cpp/index.js, где вызываются все описанные в примере функции.

Функция из нашего примера вызывается в JavaScript следующим образом:

import { NativeAppEventEmitter, NativeModules... } from 'react-native';
const DemoModule = NativeModules.DemoModule;

....

async promiseTest() {
    this.appendLine("testPromise: " + await DemoModule.testPromise());
    this.appendLine("testMap: " + JSON.stringify(await DemoModule.testMap({a: DemoModule.INT_CONSTANT, b: 2})));
    this.appendLine("testBool: " + await DemoModule.testBool(DemoModule.BOOL_CONSTANT));
    // our sample function
    this.appendLine("testString: " + await DemoModule.testString(DemoModule.STRING_CONSTANT));
}


Теперь понятно, как работают тестовые функции на стороне С++ и JavaScript. Аналогичным образом можно добавить и код любых других функций. Дальше я рассмотрю, как работают Android и iOS проекты вместе с С++.

React Native и С++ для Android


Для взаимодействия Android и С++ необходимо установить NDK. Подробная инструкция, как это сделать, есть по ссылке developer.android.com/ndk/guides
Затем внутри файла react-native-cpp/android/app/build.gradle необходимо добавить следующие настройки:
android {
        ...
        defaultConfig {
        ...
                ndk {
                        abiFilters "armeabi-v7a", "x86"
                }
                externalNativeBuild {
                        cmake {
                          cppFlags "-std=c++14 -frtti -fexceptions"
                    arguments "-DANDROID_TOOLCHAIN=clang", "-DANDROID_STL=c++_static"
                  }
                }
        }
        externalNativeBuild {
          cmake {
            path "CMakeLists.txt"
    }
  }
        sourceSets {
          main {
            java.srcDirs 'src/main/java', '../../../react-native-cpp-support/support-lib/java'
    }
        }
  splits {
          abi {
            reset()
      enable enableSeparateBuildPerCPUArchitecture
      universalApk false  // If true, also generate a universal APK
      include "armeabi-v7a", "x86"
    }
  }
        ... 
}


Только что мы сконфигурировали gradle для сборки приложения для используемых архитектур и добавили необходимые build флаги для cmake, указали файл CMAkeLists, который опишем в дальнейшем, а также добавили java-классы из Djinni, которые будем использовать.
Следующий шаг настройки Android-проекта – описание файла CMakeLists.txt. В готовом виде его можно посмотреть по пути react-native-cpp/android/app/CMakeLists.txt.
cmake_minimum_required(VERSION 3.4.1)
    
    set( PROJECT_ROOT "${CMAKE_SOURCE_DIR}/../.." )
    set( SUPPORT_LIB_ROOT "${PROJECT_ROOT}/../react-native-cpp-support/support-lib" )
    
    file( GLOB JNI_CODE "src/main/cpp/*.cpp" "src/main/cpp/gen/*.cpp" )
    file( GLOB PROJECT_CODE "${PROJECT_ROOT}/cpp/*.cpp" "${PROJECT_ROOT}/cpp/gen/*.cpp" )
    file( GLOB PROJECT_HEADERS "${PROJECT_ROOT}/cpp/*.hpp" "${PROJECT_ROOT}/cpp/gen/*.hpp" )
    
    file( GLOB DJINNI_CODE "${SUPPORT_LIB_ROOT}/cpp/*.cpp" "${SUPPORT_LIB_ROOT}/jni/*.cpp" )
    file( GLOB DJINNI_HEADERS "${SUPPORT_LIB_ROOT}/cpp/*.hpp" "${SUPPORT_LIB_ROOT}/jni/*.hpp" )
    
    include_directories(
        "${SUPPORT_LIB_ROOT}/cpp"
        "${SUPPORT_LIB_ROOT}/jni"
        "${PROJECT_ROOT}/cpp"
        "${PROJECT_ROOT}/cpp/gen"
        )
    
    add_library( # Sets the name of the library.
         native-lib
    
         # Sets the library as a shared library.
         SHARED
    
         ${JNI_CODE}
         ${DJINNI_CODE}
         ${DJINNI_HEADERS}
         ${PROJECT_CODE}
         ${PROJECT_HEADERS} )


Здесь мы указали относительные пути до support library, добавили директории с необходимым кодом С++ и JNI.

Еще одним важным шагом является добавление DjinniModulesPackage в наш проект. Для этого в файле react-native-cpp/android/app/src/main/java/com/rncpp/jni/DjinniModulesPackage.java укажем:

...
import com.rncpp.jni.DjinniModulesPackage;
...
public class MainApplication extends Application implements ReactApplication {
        ...
        @Override
  protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
                                            new MainReactPackage(),
              new DjinniModulesPackage()
    );
  }
        ...
}


Последней важной деталью является описание класса DjinniModulesPackage, который мы только что использовали в главном классе нашего приложения. Он находится по пути react-native-cpp/android/app/src/main/java/com/rncpp/jni/DjinniModulesPackage.java и содержит следующий код:
package com.rncpp.jni;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class DjinniModulesPackage implements ReactPackage {
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new DemoModule(reactContext));
        return modules;
    }
}


Наибольший интерес в вышеописанном классе представляет собой строка System.loadLibrary(«native-lib»);, благодаря которой мы загружаем в Android-приложение библиотеку с нашим нативным кодом и кодом Djinni.

Для понимания, как это работает, советую ознакомиться с jni-кодом из папки, который представляет собой jni-обертку для работы с функционалом нашего модуля, а его интерфейс описан в idl-файле.

В результате, если настроена среда разработки Android и React Native, можно собрать и запустить React Native проект на Android. Для этого выполним две команды в терминале:

npm install
npm run android

Ура! Наш проект работает!

И мы видим следующую картинку на экране Android-эмулятора (кликабельна):

Теперь рассмотрим, как работают iOS и React Native с С++.

React Native и С++ для iOS


Откроем react-native-cpp проект в XCode.

Сначала добавим ссылки на используемый в проекте Objective-C и С++ код из support library. Для этого перенесем содержимое папок react-native-cpp-support/support-lib/objc/ и react-native-cpp-support/support-lib/cpp/ в XCode проект. В результате в дереве структуры проекта будут отображены папки с кодом support library (картинки кликабельны):

Таким образом, мы добавили описания JavaScript типов из support library в проект.

Следующий шаг – добавление сгенерированных objective-c оберток для нашего тестового С++ модуля. Нам потребуется перенести в проект код из папки react-native-cpp/ios/rncpp/Generated/.

Осталось добавить С++ код нашего модуля, для чего перенесем в проект код из папок react-native-cpp/cpp/ и react-native-cpp/cpp/gen/.

В итоге дерево структуры проекта будет выглядеть следующим образом (картинка кликабельна):

Нужно убедиться, что добавленные файлы появились в списке Compile Sources внутри табы Build Phases.



(картинка кликабельна)

Последний шаг – изменить код файла AppDelegate.m, чтобы запустить инициализацию модуля Djinni при запуске приложения. А для этого потребуется изменить следующие строки кода:

...
#import "RCDjinniModulesInitializer.h"
...
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
        ...
        id<RCTBridgeDelegate> moduleInitialiser = [[RCDjinniModulesInitializer alloc] initWithURL:jsCodeLocation];
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil];

  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                      moduleName:@"rncpp"
                                            initialProperties: nil];
        ...
}


Теперь запустим наше приложение на iOS. (картинка кликабельна)


Приложение работает!

Добавление библиотеки C++ библиотеки в наш проект.


Для примера используем популярную библиотеку OpenSSL.

И начнем с Android.

Клонируем репозиторий с уже собранной библиотекой OpenSSL для Android.

Включим в файл CMakeLists.txt библиотеку OpenSSL:

....

SET(OPENSSL_ROOT_DIR /Users/andreysaleba/projects/OpenSSL-for-Android-Prebuilt/openssl-1.0.2)
SET(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/${ANDROID_ABI}/lib")
SET(OPENSSL_INCLUDE_DIR ${OPENSSL_ROOT_DIR}/include)
SET(OPENSSL_LIBRARIES "ssl" "crypto")
...
LINK_DIRECTORIES(${OPENSSL_LIBRARIES_DIR} ${ZLIB_LIBRARIES_DIR})

include_directories(
    "${SUPPORT_LIB_ROOT}/cpp"
    "${SUPPORT_LIB_ROOT}/jni"
    "${PROJECT_ROOT}/cpp"
    "${PROJECT_ROOT}/cpp/gen"
    "${OPENSSL_INCLUDE_DIR}"
    )

add_library(libssl STATIC IMPORTED)
add_library(libcrypto STATIC IMPORTED)

...

set_target_properties( libssl PROPERTIES IMPORTED_LOCATION
                    ${OPENSSL_LIBRARIES_DIR}/libssl.a )
set_target_properties( libcrypto PROPERTIES IMPORTED_LOCATION
                   ${OPENSSL_LIBRARIES_DIR}/libcrypto.a )

target_link_libraries(native-lib PRIVATE libssl libcrypto)


Затем добавим в наш С++ модуль код простой функции, возвращающий версию библиотеки OpenSSL.

В файл react-native-cpp/cpp/DemoModuleImpl.hpp добавим:

void getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> & promise) override;


В файл react-native-cpp/cpp/DemoModuleImpl.cpp добавим:
#include <openssl/crypto.h>
    ...
    void DemoModuleImpl::getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> &promise) {
        promise->resolveString(SSLeay_version(1));
    }


Осталось описать интерфейс новой функции в idl-файле `react-native-cpp-support/idl/main.djinni`:
 getOpenSSLVersion(promise: JavascriptPromise);


Вызываем скрипт `generate_wrappers.sh` из папки `react-native-cpp-support/idl/`.

Затем в JavaScript вызываем только что созданную функцию:

   
async promiseTest() {
      ...
      this.appendLine("openSSL version: " + await DemoModule.getOpenSSLVersion());
    }


Для Android все готово.
Перейдем к iOS.

Клонируем репозиторий с собранной версией библиотеки OpenSSL для iOS.

Открываем iOS проект в XCode и в настройках в табе Build Settings добавляем путь к библиотеке openssl в поле Other C Flags (пример пути на моем компьютере ниже):
-I/Users/andreysaleba/projects/prebuilt-openssl/dist/openssl-1.0.2d-ios/include

В поле Other Linker Flags добавляем следующие строки:

-L/Users/andreysaleba/projects/prebuilt-openssl/dist/openssl-1.0.2d-ios/lib
-lcrypto
-lssl


Все готово. Библиотека OpenSSL добавлена для обеих платформ.

Спасибо за просмотр!

Let's block ads! (Why?)

Scaling a Tech Newsletter to 700k Subscribers in 300 Cities: the History of Techstars Startup Digest

Entrepreneurs are constantly looking for new tools and possibilities to develop their businesses and enrich their knowledge. One of the ways of doing this is visiting themed events — meeting colleagues in real life, exchanging experiences, and communicating with potential investors.

In fact, there are thousands of tech-focused events taking place annually. The important thing is to pick the best, most useful and easily accessible ones to optimize your time and expenses.

Techstars Startup Digest solves this problem by sending its subscribers an email newsletter with a curated list of relevant and reasonably priced events for entrepreneurs. Currently, Startup Digest consists of more than 700 curators, is approaching 700K subscriptions, and is available in more than 300 cities all around the world. Today, I’d like to share its history, how the founders came up with the idea, what it looked like in the initial stages, and what it’s going through right now.


Techstars | Photo by Bill Erickson CC BY-NC

The Structure


The Startup Digest program consists of two parts:
  • Events digests by area, prepared by local curators. Digests include events for founders, developers, designers, marketing experts — anyone who is interested in startups.
  • Themed reading lists. These are organized by topic or industry, and include articles and resources chosen by the curators. Here you can find reading lists on human resources, blockchain technologies, startup communities, AI systems, and other fields.

Digests are compiled by curators who are immersed in the local startup community and are experts in a particular field or manage a business. For example, the curator for New York City is Carleen Pan, who is helping startups with marketing, launch and growth strategy. She notes that she has always loved the small community feel. “There’s a lot happening in NYC tech but no one is more than 2 or 3 connections away. People are always willing to make introductions, offer advice or help out,” Carleen said.

The curators for Startup Digest do not get paid; they do all of it on a voluntary basis. Back in 2011, one of the curators who was responsible for the Miami Digest said: “To me, this seems like a reasonable quid pro quo. I am leveraging the SD franchise and infrastructure to service the community (something I feel is important), while at the same time enhancing SD’s infrastructure/network value by my efforts.”


startupdigest.com/about

2009: How Techstars Startup Digest Began


The founder and first curator of Startup Digest was Chris McCann.

Chris moved to Silicon Valley in June 2009 and was going to many startup events (technical talks, developer meetups, hackathons, etc.) to meet other people building and working on startups. He started compiling a list for himself of all the good startups events and in November 2009 sent the first digest email with a list of startup events to his 22 of his friends.

In order to make sure the events he recommended were quality events, Chris attended every meetup he featured in the first newsletters. Because the newsletter spread awareness of the best events, most event organizers let him make a 2-minute speech about Startup Digest at the end of each event. This was how the newsletter spread from its original 22 subscribers to 12,000 subscribers in only 90 days.

To subscribe to the email newsletter in the beginning, people just had to drop a message directly to Chris or to his co-founder Brendan McManus. Back then Startup Digest did not have a website since the newsletter was essentially a signup form. A few months after the newsletter started, Chris and Brendan came up with the name Startup Digest and built a simple website for people to sign up.

Once Startup Digest had a name and a website, the newsletter was covered by TechCrunch in December 2009 and the subscriber count exploded in popularity. This was also the time when entrepreneurs in cities from around the world started reaching out to Chris and Brendan to express interest in creating their own version of Startup Digest in their local city.

The first curator after Chris was Carter Cleveland, the the co-founder of Art.sy, who became the curator for the New York City Startup Digest. In addition to being the first curator in NYC, Carter was the person who helped Chris and Brendan create the curator model to allow other entrepreneurs to create their version of Startup Digest in their own cities.

“Fundamental to Startup Digest now being in 300 cities with 700k subscribers was the creation and scaling of the curator model used to publish and maintain all of the local editions of the newsletter.

There would have been no way for us to centrally know about all of the good startups events in each of these local cities, so instead we allowed the community itself to curate the best events in a decentralized fashion,” — said Chris.


2011: First Investment


In March 2011, the startup received $200,000 in funding from the Kauffman Foundation — a nonprofit, private foundation established by Ewing Marion Kauffman in the mid-1960s. This is one of the largest private foundations in the US with an asset base of approximately $2 billion. The investment was an inflection point for Startup Digest and was used to:
  • Hire Jessica Ford full time, as the editorial director to work on scaling our content
  • Scale Startup Digest to more cities around the world
  • Create a new product focused on talent and recruiting for top startups
  • Producing video tutorials and educational content for entrepreneurs

“I joined Startup Digest as a part-time editor in August 2010. I was new to the startup world and found it very exciting to be a part of something from the early stages. It was a whirlwind during this time and the growth was pretty remarkable. We felt like we were a part of our own version of The Social Network.” — Jessica Ford, Director of Startup Digest

At that time, the Digest consisted of more than 120k subscribers in 50 cities and was growing steadily. The core audience included founders, venture capitalists, business angels and IT experts.


David Cohen introduces Techstars NYC Demo Day | Photo by Alec Perkins CC BY

2012: Acquisition by Startup Weekend


On October 3, 2012, Startup Digest was acquired by Startup Weekend, organizer of weekend events around the world where you create a real company in just 54 hours. The sum of the deal remained undisclosed. As a result, it was agreed that Startup Digest would become a non-profit part of Startup Weekend and act as a driving force to promote its initiatives.
“Our short-term goal will be to scale Startup Digest to as many communities as we can. Startup Digest is in 99 cities currently and Startup Weekend is in 335 cities. We would love to see a Startup Digest for every SW city around the globe,” Joey Pomerenke, Startup Weekend’s former CMO, said to TechCrunch.

At that point, Startup Digest reached more than 230,000 subscribers across 43 countries around the world. The company’s co-founder Brendan and the editorial director, Jessica Ford, decided to stay on the team.

Chris McCann and Chris Burnor, the CTO, moved on to developing a new product called GroupTie, the goal of which was to solve organizational issues and manage interaction between employees; the project was aimed rather at corporate clients and potential users.

2013: New deal with UP Global and Startup America


In January 2011, when Startup Digest was starting to actively develop and had attracted its first investment, the U.S. saw the launch of a program called Startup America. Together with that, there was Startup America Partnership — a collaboration of corporations, universities and foundations in support of innovation startups across the country. Just two and a half years into its existence, there were more than 13,000 startups.

In May 2013, Startup America Partnership and Startup Weekend created UP Global, a combined organization designed to become a powerful mechanism for empowering the tech entrepreneurial community. Startup Weekend and Startup Digest both joined UP Global.

The organization’s commitment was to support and train 500,000 entrepreneurs in 1,000 cities in the country over the following three years. Google for Entrepreneurs, Microsoft and the Coca-Cola Company became partners of UP Global.

2015: New life with Techstars


In June 2015, Techstars acquired UP Global as a whole, including Startup Weekend and Startup Digest. By that time, UP Global was present in 600 cities in 120 countries, while Techstars had more than 18 accelerator programs for startups around the world. More than 1,000 companies have taken part in these programs, raising over $4 billion in investments.

The goal of the acquisition was to make entrepreneurship more accessible to everyone around the world throughout the joined infrastructure of Techstars, Startup Weekend and Startup Digest.

“I think what is most exciting about all of this is the way we see Techstars and UP Global alumni or past participants coming back and becoming angel investors and/or mentors in the network. This is a virtuous cycle that we believe will have a very long-term impact on global entrepreneurship,” David Cohen said in an interview for Entrepreneur.


Photo by At Scale

Techstars Startup Digest is now part of a much larger worldwide network and continues to help entrepreneurs succeed. Organizational changes come with a set of challenges, but in following the Techstars #givefirst motto, the senior leadership team was quick to embrace Startup Digest as the free community building tool it has been since day one.

“This year we focused specifically on revamping our reading lists. We’ve moved all reading lists to a newsletter platform called Revue, which has allowed us to improve both the curator and subscriber experience.

Startup Digest curators are now able to tap into the power of the Techstars network as an added benefit for volunteering their time,” Jessica Ford said.


Techstars Startup Digest is so much more than events today. In fact, as curated source of information that’s tailored for anyone interested in startups, it’s a catalyst for local community development — it brings people together.

About the author:


Dmitry works with brands to create content and promote corporate culture at scale. Apart from it, he is curating Techstars Startup Digest, and acting as an advisor for the SXSW tech festival.

The Game of Archetypes: How Storytelling Works for Tech Brands
Scaling into English-speaking markets with Habr.com
How to Scale Your Corporate Culture
The History of SXSW: How It All Started
How the Need for Innovation Sparked the Birth of the Tech Industry
How the Early Success Stories Shaped the Modern State of the Tech Industry

Let's block ads! (Why?)

Внутренние и вложенные классы java. Часть 2

Внутренние и вложенные классы java

02.03.2017 — 2019 год

<<< Часть 1

Часть 2

Внутренние классы

Inner Classes — Внутренние классы

Внутренний класс связан с экземпляром его обрамляющего класса (из документации).

Пример внутреннего класса есть в документации.

Создадим класс:

/* Пример №7 */
//
class OuterClass {
    ...
    class InnerClass {
        ...
    }
}
 

Так в чем же отличие, спросите вы. Объявления классов и вложенных и внутренних
одинаковые в данных случаях. Отличие в том, что внутренний класс связан с внешним классом через экземпляр, или через объект класса.

Чтобы создать экземпляр внутреннего класса, нам нужно сначала создать экземпляр внешнего класса. Затем создать внутренний объект, в пределах внешнего объекта, таким образом:

OuterClass.InnerClass innerObject = outerObject.new InnerClass();

Пример:
* Пример №8 */
package inner;

/**
 *
 * @author Ar20L80
 */
public class Outer {
    
   
    class InnerClass {
        
    }
    
     Outer(){
   
    }
    
    public static void main(String[] args) {
    Outer outerObject = new Outer();
    Outer.InnerClass innerObject = outerObject.new InnerClass(); // создание экземпляра внутреннего класса
        
    }
}

По-другому мы можем написать так:

/*
Пример №9

Внутренние классы
Получить ссылку на внешний класс в конструкторе внутреннего
 */
package inner;

/**
 *
 * @author Ar20L80
 */
public class Outer5 {
       class Inner5{
            private  Outer5 myOuter;
            Inner5(){
             myOuter = Outer5.this;
            }
       }
  public static void main(String[] args){
  Outer5 outer5 = new Outer5();
  }
}


Рассмотрим свойства внутренних классов.

Внутренние классы есть смысл использовать, если они будут использовать элементы родителя,
чтобы не передавать лишнего в конструкторах.

Внутренний класс стоит использовать, когда он никому не нужен, кроме как внешнему классу.

Например, Map.Entry — нигде кроме интерфейса Map и его реализаций он не нужен.


/*
Пример №10  .
Внутренние классы
*/
package inner;

/**
 *
 * @author Ar20L80
 */
class AnyClass{} // класс от которого наследуем Inner6

public class Outer6 {
    class Inner6 extends AnyClass{
    // тут мы унаследовали внутренний класс от  AnyClass{}
    // и можем расширить функциональность класса AnyClass{} 
    // и класса Outer6
    }
}


В этом примере у нас, по сути, получилось множественное наследование, и мы можем использовать функционал класса AnyClass и функционал класса Outer6.

Дополним пример.


/*
 пример №11
 Внутренние классы

 */
package inner;

/**
 *
 * @author Ar20L80
 */
class AnyClass2{
void anyClass2Method(){}
}
public class Outer7 {
    private int iOuterVar;
    private class Inner7 extends AnyClass2
    {
        private Outer7 out7;

        public Inner7() {
            out7 = Outer7.this; // ссылка на окружающий класс
        }
        
        private int anyMethodOfInner7(){
        
            super.anyClass2Method();// можем вызвать метод нашего супер класса AnyClass2
            return out7.iOuterVar; // можем обратиться к переменным 
            // и методам Outer7
           
        }
    
    }
}


В этом примере видно, что мы можем использовать как поля и методы «окружающего» класса — Outer7, так поля и методы того класса, от которого мы наследовали внутренний класс — AnyClass2.
Это дает нам несколько большие возможности и гибкость при использовании внутреннего класса.

Совет из книги «Философия Java. Брюс Эккель. ISBN 5-272-00250-4» c. 313:

«Каждый внутренний класс может независимо наследовать определенную реализацию.

Внутренний класс не ограничен при наследовании в ситуациях, где внешний класс уже наследует реализацию.»

Чтобы использовать внутренний класс за пределами обычных методов «окружающего» класса необходимо создать объект внутреннего класса следующим способом.
ИмяВнешнегоКласса.ИмяВнутреннегоКласса.

Объект внутреннего класса сохраняет информацию о месте, где он был создан.

Продолжение следует…

Литература

Майкл Морган. «Java 2.Руководство разработчика» ISBN 5-8459-0046-8
Брюс Эккель. «Философия Java.» ISBN 5-272-00250-4
Герберт Шилдт «Java. Полное руководство. 8-е издание.» ISBN: 978-5-8459-1759-1

Ссылки:

ru.wikipedia.org
src-code.net/lokalnye-vnutrennie-klassy-java

Документация Oracle

Let's block ads! (Why?)

Символьный калькулятор на C#

Понадобилась мне как-то библиотека на c#, которая умеет делать символьные вычисления (время от времени дополняю возможностями одну математическую программу). Всякие исходники в сети найти можно и на разных языках, но вот что-то попроще и попонятнее найти не удавалось. Обратил внимание на Jasymca (Java Symbolic Calculator).

Задача


Для реализации аналога Mathcad'овского блока odesolve в программе SMath Studio понадобилось решать уравнения относительно производных. Каких-то особых ограничений на решаемую систему ОДУ нет. То, что задаёт пользователь в относительно произвольном виде должно преобразовываться к виду, удобному для численного решения ОДУ. Выглядит это должно так:

Обойтись возможностями языка c# мне тут оказалось трудным в общем случае. Не помешала бы помощь символьного движка.

Конвертация исходников


Посмотрел на исходники апплета. Вроде не очень страшно, но нужен конвертер Java в c#. В то время, когда я этим только начал заниматься нашёлся один более менее рабочий онлайновый ресурс, который сейчас вспомнить не удалось. Похоже, что это был ресурс от Tangible Software Solutions, т.к. комментарии в исходниках похожи на те, которые делает их текущий конвертер в виде программы.
Всё хорошо, но одно плохо. Ресурс конвертировал бесплатно только ограниченный размер исходника. Правда ограничение на количество строк. Очень странно, подумал я, и слепил в каждом файле все содержимое в одну строку. Далее бесплатно преобразовал однострочные исходники в c# эквивалент и восстановил нормальный вид в Visual Studio при помощи автоматического форматирования.

Доработка напильником


Надо ли говорить, что автоматический конвертер преобразует по большей части форму, а вот с содержанием нужно ещё поработать. На тот момент я понял, что мне явно не хватает уровней в обоих языках, чтобы разрешить все проблемы компиляции. Время шло, почитывал Джона Скита. Товарищ Скит внушил решимости.

В процессе борьбы за компилируемость добрым словом вспоминал конвертер. Он сделал почти всю нудную работу. Не осилил он только математическую часть в некоторых моментах. Касалось это большого класса JMath. Преобразования double в hex представление и обратно упирались в некоторых случаях в ограничение для метода BitConverter.Int64BitsToDouble(), который не работает с некоторыми unsigned long константами, например -0 (0x8000000000000000). Тут опять пришёл на помощь товарищ Скит, не без помощи которого удалось представить это значение как 1.0 / double.NegativeInfinity. Вообще, эта арифметика «бесконечно малых» до сих пор не даёт мне покоя, т.к. я не уверен в правильном интерпретировании всего того, что автор Jasymca имел в виду.

После исправления некоторых смысловых ошибок, замены работы с типами и их экземплярами и добавления консольного интерфейса удалось таки этот проект запустить. Давно хотел посмотреть на внутреннюю кухню подобных символьных программ. Одно дело какой-нибудь Symbolic C++ и совсем другое — Java или C#. Порог вхождения гораздо ниже.

На момент написания заметки выглядит калькулятор так:

Исполняемый файл доступен в репозитории. Требуется .Net 2.0. Есть зависимость от LinqBridge для работы Linq в .Net 2.0 (библиотека приложена там же).

Работает практически всё из документации автора Jasymca. Исключение составляет работа с графикой, т.е. построение графиков функций. Думаю, что проще будет написать эту часть заново, чем портировать. Не включены функции, зависящие от сторонних библиотек (LAPACK и пр.). Также присутствуют глюки, которые будут со временем вычищаться.

Заключение


Проект создавался для любителей символьной математики. При желании может быть приведён к библиотеке и использоваться в собственных проектах, где требуется несложная численная или символьная математика: работа с комплексными числами, интегрирование, дифференцирование, решение уравнений и пр. Автор Jasymca позиционировал программу как учебное пособие для тех, кто хочет создать свою программу, выполняющую символьные вычисления:
Jasymca has been developed for teaching mathematics, especially to facilitate a fast and easy entrance to computer mathematics. One of the main obstacles are pocket calculators, which prevent many students from using computers for math. Pocket calculaters are cheap and portable, while CAS-programs are often expensive and always require at least a laptop to run. Jasymca is free software and runs on almost any system equipped with a microprocessor: from mobile phones and pdas to windows/linux/macos computers, even on game consoles or internet routers.
Надеюсь, что c# вариант исходников будет полезен в чём-то и вам.

Ссылки


1. Jasymca (Java Symbolic Calculator).
2. Исходники Jasymca (zip).
3. Jasymca 2.0 — Symbolic Calculator for Java (Manual, pdf).
4. Обзор подобных Java библиотек.
5. Репозиторий проекта.

Let's block ads! (Why?)

Холдинг SoftBank продал пакет акций Nvidia из-за падения их цены, но все равно заработал $3,3 млрд

Изображение: Unsplash

На протяжение последних месяцев акции производителя видеокарт Nvidia потеряли почти 40% своей стоимости. В результате руководство японского холдинга SoftBank приняло решение избавиться от своего пакета акций Nvidia. По данным СМИ, в 2017 году фонд приобрел 4,9% акций за $4 млрд. Несмотря на падение их стоимости, прибыль от инвестиции составила $3,3 млрд.

Что происходит с акциями Nvidia в последнее время


В течение последних лет компания-разработчик видеокарт демонстрировала стремительный рост: с 2015 года до зимы 2018 года стоимость ее акций увеличилась более чем на 1100%. Во многом стремительный рост был обеспечен взрывным развитием сферы криптовалют – «железо» от Nvidia активно использовалось для их майнинга.

Рост акций был столь стремительным, что некоторые аналитики заявляли о признаках экономического пузыря. Похожая динамика акций некоторых технологических компаний наблюдалась в момент, близкий краху доткомов.

Замедления развития рынка криптовалют, масштабное снижения стоимости биткоина и затухание интереса к майнингу, серьезно ударило по рыночным позициям Nvidia. Из-за снижения прибыльности и снижения интереса к майнингу на рынке оказалось много б/у видеокарт, что привело к падению продаж – на складах копились произведенные, но не проданные новые видеокарты. Также негативное влияние на динамику акций Nvidia оказало замедление экономического роста в Китае.

Глава Nvidia Жэнь-Сунь Хуан (Jensen Huang) назвал все это «похмельем после бурного роста криптовалют», последствия которого продлились дольше ожидаемого. В итоге акции Nvidia упали в цене с $292 в сентябре 2018 года до $153 на текущей неделе.

Инвестиции SoftBank: миллиардная прибыль несмотря на проблемы


Подконтрольный SoftBank Vision Fund объявил об инвестициях в Nvidia в мае 2017 года. По данным Bloomberg в тот момент фонд купил пакет на 4,9% акций за $4 млрд.

Как заявил глава SoftBank Масаёси Сон, использование стратегий торгов, нацеленных на минимизацию рисков колебаний цены активов, позволили продать весь пакет по средней цене $218 за акцию. Покупка в 2017 году произошла по цене $105 за акцию. Таким образом, SoftBank заработал на инвестициях в Nvidia $3,3 млрд.

Заключение


По словам Сона, фонд продал акции Nvidia, чтобы сконцентрироваться на «инвестициях в многообещающие компании-единороги».

Nvidia лишь одна компаний из растущего списка производителей чипов и компьютерного железа, которые страдают от текущей экономической ситуации. Так в последние месяцы стало известно о снижении продаж iPhone – возможные потери Apple оцениваются в $9 млрд. Проблемы компании больно бьют по основным поставщикам компании – к примеру с падением своих акций столкнулась корпорация Intel, которая поставляет процессоры для iPhone.

Другие материалы по теме финансов и фондового рынка от ITI Capital:


Let's block ads! (Why?)

[Из песочницы] Начинаем FPGA на Python

Технология FPGA (ПЛИС) в настоящее время обретает большую популярность. Растёт количество сфер применения: помимо обработки цифровых сигналов, FPGA используются для ускорения машинного обучения, в blockchain технологиях, обработке видео и в IoT.

Данная технология имеет один существенный минус: для программирования используются довольно старые и специфичные языки описания цифровой аппаратуры Verilog и VHDL. Это осложняет вхождение новичка в FPGA и для работодателя найти специалиста с этими специфичными знаниями на рынке труда трудно. С другой стороны популярный высокоуровневый язык программирования Python с фреймворком MyHDL делают программирование FPGA простым и приятным. Тем более людей знающих Python на порядок больше специалистов владеющих Verilog/VHDL. Серией статей я хочу показать как легко и просто войти в область FPGA зная Python и начать делать настоящие сложные FPGA проекты на этом языке.
Во первых нам понадобится сам python версии 3.6 (здесь и далее все операции производятся в ОС Ubuntu 18.04).

Устанавливаем myhdl:

pip3 install myhdl

В качестве «Hello World!» Напишем простую программу, которая заставляет загораться светодиоды от нажатия на кнопку. В мире микропроцессоров «Hello World!» это программа, которая мигает одним светодиодом, в мире FPGA Hello World — это мигание тысячи светодиодов. На плате есть только четыре светодиода, так что мигать будем только ими в зависимости от нажатия кнопки. Важно отметить, что весь код в FPGA в отличие от микроконтроллеров выполняется одновременно, все диоды зажигаются и гаснут одновременно. А не последовательно в случае микроконтроллеров. В роли подопытного используется плата WaveShare OpenEPM1270 с плис Altera Max II EPM1270T144C5 на борту.

Создаём новый python файл:

from myhdl import *
from random import randrange
def led_blinker(input1, led1, led2, led3, led4):
    @always_comb
    def on_off_led():
        if input1 == 1:
            led1.next = 1
            led2.next = 1
            led3.next = 0
            led4.next = 0
        else:
            led1.next = 0
            led2.next = 0
            led3.next = 1
            led4.next = 1
    return on_off_led


Чтобы узнать правильно ли работает наш код нужно средство верификации. По сути любая программа для FPGA это обработчик цифровых сигналов, соответственно разработчику необходимо убедиться что он правильно указал что делать микросхеме. Делается это через симуляцию, для этого нужно установить программу которая будет отображать обработанные сигналы. Таких программ довольно много, но на мой взгляд самая лучшая на данным момент бесплатная GTKWave. Ставится из терминала:
sudo apt-get install gtkwave

Далее в файле с прошивкой следует описать тестовое окружение. Это тоже питоновская функция:
def test():
    input1, led1, led2, led3, led4 = [Signal(bool(0)) for i in range(5)]
    test = led_blinker(input1, led1, led2, led3, led4)
    @always(delay(10))
    def gen():
        input1.next = randrange(2)
return test, gen


Здесь тестовое окружение генерирует случайную последовательность из нулей и единиц (используется питоновский модуль random).
def simulate(timesteps):
    tb = traceSignals(test)
    sim = Simulation(tb)
    sim.run(timesteps)


И инициализируем симулятор, протаскивая туда функцию окружения test_inverter. Таким образом получается матрёшка inverter → test_inverter → simulate(время в условных единицах).

После запуска скрипта в рабочей папке создаться .vcd файл и его следует пропустить через gtkwave, в терминале: gtkwave test_invereter.vcd.

В итоге сгенерировалась случайная последовательность входных сигналов input1, и то как обработала эти сигналы функция led_blinker.

После того как мы убедились, что логика отработал ровно так как мы хотели, далее следует эту функцию закинуть в FPGA. Я привык работать с микросхемами фирмы Intel (ранее Altera), данная последовательность действий аналогична и для микросхем других производителей с соответствующими САПР. На микросхему ПЛИС заливается бинарный файл, который создаёт компилятор производителя микросхемы, для Intel это Quartus, для Xilinx Vivado. Компиляторы умеют работать только с кодом в VHDL/Verilog, поэтому питоновский код следует транслировать в любой из этих языков (не принципиально в какой).

def convert():
    input1, led1, led2, led3, led4 = [Signal(bool(0)) for i in range(5)]
    toVerilog(led_blinker, input1, led1, led2, led3, led4)
convert()


В этом примере код транслируется в Verilog. Результат в файле led_blinker.v, его и надо будет дать Quartus для генерации прошивки FPGA.

Скачать Quartus можно с сайта fpgasoftware.intel.com, нужна бесплатная версия Lite, её нам будет достаточно. Качаем базовую версию размер 9 GB.

Установка Quartus не должна вызвать сложностей для обычного пользователя Linux. После установки необходимо задать некоторые параметры в системе, чтобы можно было пользоваться устройством для зашивки программы ПЛИС — программатором:

1. Создаем udev правило. Для этого создаем новый файл /etc/udev/rules.d/51-altera-usb-blaster.rules со следующим с содержанием:


# USB-Blaster
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6001", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6002", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6003", MODE="0666"
# USB-Blaster II
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6010", MODE="0666"
SUBSYSTEM=="usb", ATTR{idVendor}=="09fb", ATTR{idProduct}=="6810", MODE="0666"


Перезагружаем udev, используя udevadm:
sudo udevadm control --reload

2. Разрешаем non-root доступ к устройству USB-Blaster. Для этого создаем файл /etc/udev/rules.d/altera-usb-blaster.rules со строкой:
ATTR{idVendor}=="09fb", ATTR{idProduct}=="6001", MODE="666"

Это даёт rw-rw-rw- доступ к программатору.

3. Конфигурируем jtagd. Quartus использует для работы демона jtagd, который связывет софт с устройством программатора. Копируем описание из вашей директории с Quartus:

sudo mkdir /etc/jtagd
sudo cp <Quartus install path>/quartus/linux64/pgm_parts.txt /etc/jtagd/jtagd.pgm_parts

Запускаем Quartus и создаём новый проект «File» — «New project wizard», набираем имя проекта.

Далее нажимаем Next. И в меню Add Files подключаем сгенерированный verilog файл с расширением .v. Таким образом, если verilog файл будет редактироваться из python файла то он автоматически будет подхватываться Quartus, Далее попадаем в меню выбора девайса, в нашем случае это MAX II EMP1270T144C5 и ещё пару раз next. Проект создан.

В Project Navigator заходим в меню файлов и правой кнопкой мыши задаем наш verilog файл «Set as top-level entity».

Компилируем проект. Теперь в меню «Assignments-Pin Planner» конфигурируем пины на микросхеме:

Ещё раз компилируем. Теперь всё готово к программированию: Tools-Programmer. Подключаем программатор и питание к плате, в Hardware Setup выбираем свой USB-Blaster, задаём галочки как изображено на рисунке и Start.

После того как Programmer отрапортовал Successful. Можно посмотреть результат на плате:

Заключение


В этом уроке рассмотрено как создавать рабочее окружение и первый простой проект на ПЛИС на языке программирования Python.

Рассмотрено:

  • как устанавливать:
    • myHDL;
    • GTKWave;
    • Quartus;
  • Произведена настройка программатора USB Blaster в Ubuntu;
  • Разработан проект на ПЛИС на python;
  • Произведено тестирование и верификация проекта;
  • Скомпилирован проект под ПЛИС;
  • Загружен проект на ПЛИС.

Let's block ads! (Why?)

[Перевод] Разбираемся с асинхронностью в JavaScript [Перевод статьи Sukhjinder Arora]

Привет, Хабр! Представляю вашему вниманию перевод статьи «Understanding Asynchronous JavaScript» автора Sukhjinder Arora.


От автора перевода: Надеюсь перевод данной статьи поможет вам ознакомиться с чем-то новым и полезным. Если статья вам помогла, то не поленитесь и поблагодарите автора оригинала. Я не претендую на звание профессионального переводчика, я только начинаю переводить статьи и буду рад любым содержательным фидбекам.

JavaScript — это однопоточный язык программирования, в котором может быть выполнено только что-то одно за раз. То есть, в одном потоке движок JavaScript может обработать только 1 оператор за раз.

Хоть однопоточные языки и упрощают написание кода, поскольку вы можете не беспокоиться о вопросах параллелизма, это также означает, что вы не сможете выполнять долгие операции, такие как доступ к сети, не блокируя основной поток.

Представьте запрос к API для получения некоторых данных. В зависимости от ситуации, серверу может потребоваться некоторое время для обработки вашего запроса, при этом будет заблокировано выполнение основного потока из-за чего ваша веб-страница перестанет отвечать на запросы к ней.

Здесь то и вступает в игру асинхронность JavaScript. Используя асинхронность JavaScript(функции обратного вызова(callback’и), “промисы” и async/await) вы можете выполнять долгие сетевые запросы без блокирования основного потока.

Несмотря на то, что не обязательно изучать все эти концепции, чтобы быть хорошим JavaScript-разработчиком, полезно их знать.

И так, без лишних слов, давайте начинать.

Как работает синхронный JavaScript?


Прежде чем мы углубимся в работу асинхронного JavaScript, давайте для начала разберемся как выполняется синхронный код внутри движка JavaScript. Например:
const second = () => {
  console.log('Hello there!');
}
const first = () => {
  console.log('Hi there!');
  second();
  console.log('The End');
}
first();

Для того, чтобы разобраться как код выше выполняется внутри движка JavaScript, нам следует понимать концепцию контекста выполнения и стека вызовов(так же известный как стек выполнения).

Контекст выполнения


Контекст выполнения — это абстрактное понятие окружения в котором код оценивается и выполняется. Всякий раз, когда какой-либо код выполняется в JavaScript он запускается в контексте выполнения.

Код функции выполняется внутри контекста выполнения функции, а глобальный код в свою очередь выполняется внутри глобального контекста выполнения. Каждая функция имеет свой собственный контекст выполнения.

Стек вызовов


Под стеком вызовов подразумевается стек со структурой LIFO(Last in, First Out/Последний вошел, первый вышел), который используется для хранения всех контекстов выполнения, созданных на протяжении исполнения кода.

В JavaScript имеется только один стек вызовов, так как это однопоточный язык программирования. Структура LIFO означает, что элементы могут добавляться и удаляться только с вершины стека.

Давайте теперь вернемся к фрагменту кода выше и попробуем понять, как движок JavaScript его выполняет.

const second = () => {
  console.log('Hello there!');
}
const first = () => {
  console.log('Hi there!');
  second();
  console.log('The End');
}
first();

И так, что же здесь произошло?


Когда код начал выполняться, был создан глобальный контекст выполнения(представленный как main()) и добавлен на вершину стека вызовов. Когда встречается вызов функции first() он так же добавляется на вершину стека.

Далее, на вершину стека вызовов помещается console.log('Hi there!'), после выполнения он удаляется из стека. После этого мы вызываем функцию second(), поэтому она помещается на вершину стека.

console.log('Hello there!') добавлен на вершину стека и удаляется из него по завершению выполнения. Функция second() завершена, она также удаляется из стека.

console.log('The End') добавлен на вершину стека и удален по завершению. После этого функция first() завершается и также удаляется из стека.

Выполнение программы заканчивается, поэтому глобальный контекст вызова(main()) удаляется из стека.

Как работает асинхронный JavaScript?


Теперь, когда мы имеем общее представление о стеке вызовов и о том, как работает синхронный JavaScript, давайте вернемся к асинхронному JavaScript.

Что такое блокирование?


Давайте предположим, что мы выполняем обработку изображения или сетевой запрос синхронно. Например:
const processImage = (image) => {
  /**
  * Выполняем обработку изображения
  **/
  console.log('Image processed');
}
const networkRequest = (url) => {
  /**
  * Обращаемся к некоторому сетевому ресурсу
  **/
  return someData;
}
const greeting = () => {
  console.log('Hello World');
}
processImage(logo.jpg);
networkRequest('www.somerandomurl.com');
greeting();

Обработка изображения и сетевой запрос требует времени. Когда функция processImage() вызвана её выполнение потребует некоторого времени, в зависимости от размера изображения.

Когда функция processImage() выполнена она удаляется из стека. После нее вызывается и добавляется в стек функция networkRequest(). Это снова займет некоторое время прежде чем завершить выполнение.

В конце концов, когда функция networkRequest() выполнена, вызывается функция greeting(), поскольку она содержит только метод console.log, а этот метод, как правило, выполняется быстро, функция greeting() выполнится и завершится мгновенно.

Как вы видите, нам нужно ждать пока функция(такие как processImage() или networkRequest()) завершится. Это означает, что такие функции блокируют стек вызовов или основной поток. По итогу мы не можем выполнить другие операции, пока код выше не будет выполнен.

Так какое же решение?


Самое простое решение — это асинхронные функции обратного вызова. Мы используем их, чтобы сделать наш код неблокируемым. Например:
const networkRequest = () => {
  setTimeout(() => {
    console.log('Async Code');
  }, 2000);
};
console.log('Hello World');
networkRequest();

Здесь я использовал метод setTimeout для того чтобы имитировать сетевой запрос. Пожалуйста, помните, что setTimeout не является частью движка JavaScript, это часть так называемого web API(в браузере) и C/C++ APIs (в node.js).

Для того чтобы понять, как этот код выполняется, мы должны разобраться с ещё несколькими понятиями, такими как цикл обработки событий и очередь обратных вызовов(также известная как очередь задач или очередь сообщений).

Цикл обработки событий, web API и очередь сообщений/очередь задач не являются частью движка JavaScript, это часть браузерной среды выполнения JavaScript или среды выполнения JavaScript в Nodejs(в случае Nodejs). В Nodejs, web APIs заменяется на C/C++ APIs.

Теперь давайте вернемся назад, к коду выше, и посмотрим, что произойдет в случае асинхронного выполнения.

const networkRequest = () => {
  setTimeout(() => {
    console.log('Async Code');
  }, 2000);
};
console.log('Hello World');
networkRequest();
console.log('The End');

Когда код приведенный выше загружается в браузер console.log('Hello World') добавляется в стек и удаляется из него по завершению выполнения. Далее встречается вызов функции networkRequest(), он добавляется на вершину стека.

Следующая вызывается функция setTimeout() и помещается на вершину стека. Функция setTimeout() имеет 2 аргумента: 1) функция обратного вызова и 2) время в миллисекундах.

setTimeout() запускает таймер на 2 секунды в окружении web API. На этом этапе, setTimeout() завершается и удаляется из стека. После этого, в стек добавляется console.log('The End'), выполняется и удаляется из него по завершению.

Тем временем таймер истек, теперь обратный вызов добавляется в очередь сообщений. Но обратный вызов не может быть немедленно выполнен, и именно здесь в процесс вступает цикл обработки событий.

Цикл обработки событий


Задача цикла обработки событий заключается в том чтобы следить за стеком вызовов и определять пуст он или нет. Если стек вызовов пустой, то цикл обработки событий заглядывает в очередь сообщений, чтобы узнать есть ли обратные вызовы, которые ожидают своего выполнения.

В нашем случае очередь сообщений содержит один обратный вызов, а стек выполнения пуст. Поэтому цикл обработки событий добавляет обратный вызов на вершину стека.

После console.log('Async Code') добавляется на вершину стека, выполняется и удаляется из него. На этом моменте обратный вызов выполнен и удален из стека, а программа полностью завершена.

События DOM


Очередь сообщений также содержит обратные вызовы от событий DOM, такие как клики и “клавиатурные” события. Например:
document.querySelector('.btn').addEventListener('click',(event) => {
  console.log('Button Clicked');
});

В случае с событиями DOM, обработчик событий находится в окружении web API, ожидая определенного события(в данном случае клик), и когда это событие происходит функция обратного вызова помещается в очередь сообщений, ожидая своего выполнения.

Мы изучил как выполняются асинхронные обратные вызовы и события DOM, которые используют очередь сообщений для хранения обратных вызовов ожидающих своего выполнения.

ES6 Очередь микротасков


Прим. автора перевода: В статье автор использовал message/task queue и job/micro-taks queue, но если перевести task queue и job queue, то по идее это получается одно и то же. Я поговорил с автором перевода и решил просто опустить понятие job queue. Если у вас есть какие-то свои мысли на этот счет, то жду вас в комментариях

ES6 представил понятие очередь микротасков, которые используются “промисами” в JavaScript. Разница между очередью сообщений и очередью микротасков состоит в том, что очередь микротасков имеет более высокий приоритет по сравнению с очередью сообщений, это означает, что “промисы” внутри очереди микротасков будут выполняться раньше, чем обратные вызовы в очереди сообщений.

Например:

console.log('Script start');
setTimeout(() => {
  console.log('setTimeout');
}, 0);
new Promise((resolve, reject) => {
    resolve('Promise resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));
console.log('Script End');

Вывод:
Script start
Script End
Promise resolved
setTimeout

Как вы можете видеть “промис” выполнился раньше setTimeout, все это из-за того, что ответ “промиса” хранится внутри очереди микростасков, которая имеет более высокий приоритет, нежели очередь сообщений.

Давайте разберем следующий пример, на этот раз 2 “промиса” и 2 setTimeout:

console.log('Script start');
setTimeout(() => {
  console.log('setTimeout 1');
}, 0);
setTimeout(() => {
  console.log('setTimeout 2');
}, 0);
new Promise((resolve, reject) => {
    resolve('Promise 1 resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));
new Promise((resolve, reject) => {
    resolve('Promise 2 resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));
console.log('Script End');

Вывод:
Script start
Script End
Promise 1 resolved
Promise 2 resolved
setTimeout 1
setTimeout 2

И снова оба наших “промиса” выполнились раньше обратных вызовов внутри setTimeout, так как цикл обработки событий считает задачи из очереди микротасков важнее, чем задачи из очереди сообщений/очереди задач.

Если во время выполнения задач из очереди микротасков появляется ещё один “промис”, то он будет добавлен в конец этой очереди и выполнен раньше обратных вызовов из очереди сообщений, и не важно сколько времени они ожидают своего выполнения.

Например:

console.log('Script start');
setTimeout(() => {
  console.log('setTimeout');
}, 0);
new Promise((resolve, reject) => {
    resolve('Promise 1 resolved');
  }).then(res => console.log(res));
new Promise((resolve, reject) => {
  resolve('Promise 2 resolved');
  }).then(res => {
       console.log(res);
       return new Promise((resolve, reject) => {
         resolve('Promise 3 resolved');
       })
     }).then(res => console.log(res));
console.log('Script End');

Вывод:
Script start
Script End
Promise 1 resolved
Promise 2 resolved
Promise 3 resolved
setTimeout

Таким образом все задачи из очереди микротасков будут выполнены раньше задач из очереди сообщений. То есть цикл обработки событий сначала очистит очередь микростасков, а только после этого начнет выполнение обратных вызовов из очереди сообщений.

Заключение


И так, мы изучил как работает асинхронный JavaScript и другие понятия, такие как стек вызовов, цикл обработки событий, очередь сообщений/очередь задач и очередь микротасков, которые вместе представляют собой среду выполнения JavaScript.

Let's block ads! (Why?)

Как настроить Continuous Deployment для своего проекта: личный опыт

Перевели для вас статью Юлиуса Минмо о настройке непрерывной интеграции (Continuous Deployment) для своего проекта. Автоматизация позволяет сэкономить кучу времени и сил. Статья будет полезна, в первую очередь, начинающим программистам.

Непрерывная интеграция — отличная штука. Один раз коммитим проект и далее все происходит в автоматическом режиме, наблюдение за этим процессом просто гипнотизирует. В этой статье я покажу, как можно все настроить для домашнего проекта.

Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».

Skillbox рекомендует: Онлайн-курс «Профессия Frontend-разработчик».


Итак, для начала давайте посмотрим на схему, где объясняется разница между Continuous Delivery и Continuous Deployment.

В случае с домашним проектом выбираем Continuous Deployment, поскольку никто, кроме вас, с ним (проектом) не работает и никто от него не зависит. Ну а поскольку в большинстве случаев хочется, чтобы изменения были немедленно развернуты, то выбор очевиден. Если же вам позже захочется изменить процесс, вы всегда сможете это сделать.

Вы изучите следующее:

  • Как сделать Dockerfile.
  • Как выгрузить проект на GitHub.
  • Как автоматически построить образ docker на Docker Hub.
  • Как автоматически загрузить и запустить образ с Watchtower.

Что требуется:
  • Базовое понимание того, что представляют собой Docker и Dockerfile.
  • Установленный Git.
  • Учетная запись на <a href='https://hub.docker.com/">Docker Hub

Сервер (физический или виртуальный) с запущенным Docker.

Вот мои репозиторий GitHub и Docker Hub, с которыми я работаю.

Почему я использую Docker?


Он дает возможность использовать одно и то же окружение для разных процессов, что исключает появление гейзенбагов и проблемы «это работает только на моей машине». Контейнеры изолированы, что хорошо с точки зрения кибербезопасности. Есть и больше преимуществ, но, на мой взгляд, эти два являются главными.

Настройка Dockerfile

Сначала нам нужен Dockerfile для проекта. Этот файл всегда называется именно так и не имеет расширения. Он всегда находится в главной директории проекта.

Он начинается с оператора FROM, который сообщает Docker, с какого базового образа мы начинаем. Вы можете провести аналогию с живописью. Можем представить себе этот образ как готовый холст с нарисованным фоном и отсутствующим главным элементом композиции (вашей программой).

Далее копируем файлы проекта в контейнер при помощи команды COPY…

Она позволяет забрать файлы из начального расположения в текущее — конечно, внутри контейнера.

Далее необходимо установить зависимости, для этого я использую Python PIP. Главное, что нужно запомнить, — это запуск команд в контейнере с RUN.

From python:3.7
COPY..
RUN pip install -r requirements.txt

Все просто, правда? Теперь можно запускать программу в контейнере.

CMD [«python», "./my_script.py"]

Теперь все, вы закончили Dockerfile и можете вручную создать образ и контейнер. Сейчас просто пропустим этот момент.

Теперь давайте создадим репозиторий в GitHub, но помните, что строку “Initialize this repository with a README” не нужно трогать.

Теперь копируем URL.

Открываем cmd/shell корневой директории проекта. Теперь необходимо инициализировать репозиторий, добавить файлы, сконфигурировать remote-режим, закоммитить файлы и отправить проект на GitHub.

git init
git add *
git remote add origin github.com/.git
git commit -a -m «Make Dockerfile ready for CD»
git push -u origin master

Если все ОК, GitHub-репозиторий будет выглядеть вот так:

Мы на полпути к успеху!

Теперь нужно подключить GitHub к Docker Hub. Для этого нужно отправиться в настройки учетной записи.

Скролим вниз и подключаемся.

Теперь создаем репозиторий в Docker Hub.

Называем свой репо и кликаем по иконке GitHub или Bitbucket. Потом выбираем организацию (обычно это ваш ник) и название проекта. При желании настройки можно изменить.

Ну а теперь последний шаг — здесь нам необходим Watchtower на целевой машине. Это программа, которая позволяет автоматизировать процесс. Если появляется апдейт, то Watchtower убирает оригинальный контейнер и создает контейнер из нового образа с такими же настройками.

Хорошая новость в том, что можно установить Watchtower с Docker, для этого необходимо ввести в терминал такую команду:

docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock v2tec/watchtower

И теперь запускаем контейнер для своего проекта!

docker run -d --name <my-project> /<my-project>

-d позволяет программе работать в фоне, так что она не закроется, если вы закроете терминал.

Завершая сказанное, если вы отправите коммит к репозиторию GitHub, Docker Hub автоматически создаст образ Docker. Затем с ним уже будет взаимодействовать Watchtower.

Что касается тестов, то вы сможете использовать Travis CI. Вы можете прочитать об этом здесь, но суть в том, что вы добавляете в свой репозиторий еще один файл, в котором есть инструкции для внешнего сервера для выполнения модульных тестов или любые другие инструкции.

Skillbox рекомендует:

Let's block ads! (Why?)

[Из песочницы] Опыт разработки свободного приложения для коллекционеров OpenNumismat

Хочу поделиться своим опытом разработки настольного приложения для коллекционеров с открытым исходным кодом.

По роду своей основной деятельности я разрабатываю ПО для встраиваемых систем и редко пересекаюсь с конечными пользователями. Поэтому речь пойдет об особенностях, с которыми я столкнулся при разработке некоммерческого приложения для широкого круга пользователей, которые стали для меня открытием.

image

Описание


OpenNumismat как видно из названия предназначен, в первую очередь, для учета коллекции монет. Но вполне подходит и для других видов коллекционирования — марок, открыток, значков и более экзотических вещей.

Приложение написано на языке Python, для хранения данных используется база данных на SQLite, для интерфейса, доступа к данным и многого другого — PyQt, для генерации отчетов — Jinja2, для построения графиков статистики — Matplotlib. Несмотря на мой первоначальный скепсис этого оказалось достаточно для обеспечения приемлемой производительности — несколько тысяч записей с изображениями обрабатываются без каких-либо заметных тормозов.
Поскольку все компоненты кроссплатформенные, то и OpenNumismat имеет сборки для Windows, Linux (Debian/Ubuntu), macOS.

Так же есть версия для Android, в настоящее время заброшенная. Ей на смену пришло PWA (Progressive Web App), позволяющее просматривать свою коллекцию в любом браузере — используется SQLite.js. Разработать PWA оказалось гораздо легче создания аналогичного по возможностям нативного Android-приложения. Кроме того, оно сразу доступна на всех платформах.

Конкуренты


Сейчас существует масса приложений для коллекционеров и, в частности, нумизматов. Большинство из них «выглядит так, как будто кто-то получил свою первую версию Firefox и использовал ее».

В США многие используют коммерческий софт, несомненным плюсом которого является наличие готовых баз существующих монет. Пользователю остается только отмечать имеющиеся монеты и заносить информацию о покупке — цена, дата и т.д.

Аналогичные возможности предоставляют мобильные приложения (большинство для Android) и веб-сервисы. OpenNumismat больше нацелен на создание каталога своей собственной уникальной коллекции, а не на «закрытие дырок в готовом альбоме», как выражаются некоторые нумизматы.

Но самый главный конкурент — это Microsoft. Подавляющая часть коллекционеров использует Excel не смотря на то, что он плохо работает с изображениями. Так же некоторые используют Access или 1С и создают свою базу данных коллекции со всем что им требуется. Но это все же требует некоторой подготовки.

Для привлечения пользователей реализован импорт из наиболее популярных альтернатив.
Что касается Linux и macOS, то для этих платформ аналогов практически нет.

Преимущества открытости


Основным преимуществом открытости кода оказалась возможность бесплатного использования сторонних сервисов для распространения, перевода, баг-трекинга. Пулл реквестов же, к моему огорчению, было всего три.

Неожиданно для меня еще одним преимуществом оказалось потенциальная возможность убедить скептиков в безопасности программы — на форумах утверждают, что проверяли код grep'ом и никаких подозрений нет.

Используемое окружение


Изначально для распространения OpenNumismat был выбран Google Code, который предоставлял удобный баг-трекинг, мультиязычную вики для документации, загрузку бинарников и конечно git-репозиторий. Но начиная с 2013 Google Code начал умирать. Для переезда выбор стоял между SourceForge и GitHub. Оба сервиса предоставляют возможность создавать сайты проекта (документацию из вики пришлось переделать в статические страницы сайта), но скачивание бинарников с SourceForge выглядит страшновато для неподготовленного пользователя. Поэтому выбор пал на GitHub.

Баг-трекер GitHub требует регистрации, что не так удобно пользователям, далеким от программирования. Поэтому было решено использовать сторонний сервис. Был найден Idea.Informer (Реформал для русскоязычной аудитории) — бесплатный и удобный сервис, но в настоящее время не развивается и это вызывает ряд трудностей (Idea.Informer перестал отсылать письма и не поддерживает SSL).

Изначально OpenNumismat поддерживал английский и русский язык. Но уже через год после первого релиза из Венесуэлы поступило предложение перевести на испанский. Потом появились предложения перевода и на другие языки. Изначально перевод осуществлялся с помощью инструмента Qt Linguist. Для удобства волонтеров сейчас используется онлайн-сервис, предоставляющий удобную инфраструктуру для открытых проектов. В настоящее время OpenNumismat переведен уже на 15 языков.

Для продвижения OpenNumismat пришлось изучить некоторые трюки CEOшников. Так использую семантическую разметку удалось продвинуть и улучшить отображение сайта приложения в поисковиках.

Проблемы версий библиотек


В настоящее время для Windows версии используется PyQt версии 5.5.1. Переход на более свежую версию затруднен заменой Qt WebKit на Qt WebEngine, который до сих пор не умеет делать предпросмотр печатаемых страниц, используемый для работы с отчетами.

А переход нужен для использования Qt Charts для построения графиков статистики, но он не доступен в используемой версии. Конечно можно было бы его и собрать отдельно, но руки не дошли. Поэтому для статистики используется Python пакет Matplotlib.

Также свежие версии Python не поддерживают Windows XP, который еще используется небольшим количеством пользователей.

Версия для Linux вообще может использовать любую комбинация версий PyQt, Python, Matplotlib. Поэтому на некоторых конфигурациях может не работать статистика, на других ограничен просмотр отчетов.

Для сборки под macOS было два варианта: использовать официальную сборку Python с требуемыми пакетами или MacPorts. В первом варианте возникают описанные проблемы с отчетами, так же процесс сборки не удалось полностью настроить, во втором возникли проблемы с Matplotlib, но MacPorts имеет сборку свежей версии PyQt с Qt WebKit. В настоящее время используется вариант с MacPorts.

Так же хочу отметить, что SQLite плохо поддерживает не-ASCII символы из коробки. Это вызывало проблемы при сортировке у пользователей, использующих алфавиты где используются символы, отличные от латинского и русского (например, украинский и каталонский — пользователи этих стран особенно обращали внимание на проблему). Перенос сортировки из запросов SQL в Qt не только решил проблему, но и повысил производительность.

Обратная связь


Отзывы и обратная связь — это самая приятная часть работы. Лучше всего OpenNumismat был принят в испанском сообществе нумизматов. Русские же удивили меня отзывами вроде: «Программа ждет, когда коллекция заполнится максимально, а потом сообщает КУДА НАДО». Видимо опасаются «русских хакеров». Часто приходится оправдываться перед скептиками, которые даже не собираются использовать OpenNumismat — это порой расстраивает.

Многие пользователи пишут, чтобы просто поблагодарить, предлагают новые идеи, просят помочь разобраться в работе приложения. Большинство же описывают ошибки и проблемы в работе приложения, которые, несомненно часто возникают, ведь разработка и тестирование ведется одним человеком.

Наиболее частый вопрос — «у меня демо версия, как мне получить полную». Связано это с тем что при первом запуске открывается демонстрационная база данных с названием demo. Видимо это сильно смущает новых пользователей.

Довольно часто предлагают добавить весьма специфичную функциональность, которая может быть полезна отдельным пользователям, но большинству может показаться совершенно лишней и усложняющей. Или же изменяет основную логику организации данных. На такие предложения приходится отвечать отказом или, что чаще, игнорировать сообщение.

Порой приходят сообщения об ошибках, которые мне не удается воспроизвести. Если контакт с пользователем установлен, то проблема как правило решается после некоторой переписки и совместных экспериментов. Иногда выйти на контакт не удается, и ошибка остается неисправленной и, возможно, никем более не замеченной.

Так же приходят письма от создателей нумизматических сайтов и электронных каталогов, которые пытаются произвести революцию, но заметных подвижек пока нет.

Заключение


Разработка ведется уже более 6 лет. Многого удалось достичь, но приложение еще далеко от совершенства. Есть еще очень много идей, которые хотелось бы воплотить.

Получать положительные отклики, безусловно, приятно и хочется сделать для пользователей по-настоящему полезный инструмент. А поскольку я и сам являюсь активным пользователем OpenNumismat, то останавливаться на достигнутом не собираюсь.

Let's block ads! (Why?)

[Перевод] Настройка НА-кластера Kubernetes на «голом железе» с kubeadm. Часть 1/3

Всем привет! В этой статье я хочу упорядочить информацию и поделиться опытом создания и использования внутреннего кластера Kubernetes.

За последние несколько лет эта технология оркестровки контейнеров сделала большой шаг вперед и стала своего рода корпоративным стандартом для тысяч компаний. Некоторые используют ее в продакшене, другие просто тестируют на проектах, но страсти вокруг нее, как ни крути, пылают нешуточные. Если еще ни разу ее не использовали, самое время начать знакомство.


0. Вступление

Kubernetes — это масштабируемая технология оркестровки, которая может начинаться с установки на одной ноде и достигать размеров огромных НА-кластеров на основе нескольких сотен нод внутри. Большинство популярных облачных провайдеров представляют разные виды реализации Kubernetes — бери и пользуйся. Но ситуации бывают разные, и есть компании, которые облака не используют, а получить все преимущества современных технологий оркестровки хотят. И тут на сцену выходит инсталляция Kubernetes на «голое железо».


1. Введение

В этом примере мы создадим НА-кластер Kubernetes с топологией на несколько мастер-нод (multi masters), с внешним кластером etcd в качестве базового слоя и балансировщиком нагрузки MetalLB внутри. На всех рабочих нодах будем развертывать GlusterFS как простое внутреннее распределенное кластерное хранилище. Также попытаемся развернуть в нем несколько тестовых проектов, используя наш личный реестр Docker.

Вообще, есть несколько способов создать НА-кластер Kubernetes: трудный и углубленный путь, описанный в популярном документе kubernetes-the-hard-way, или более простой способ — с помощью утилиты kubeadm.

Kubeadm — это инструмент, созданный сообществом Kubernetes именно, чтобы упростить установку Kubernetes и облегчить процесс. Ранее Kubeadm рекомендовали только для создания небольших тестовых кластеров с одной мастер-нодой, для начала работы. Но за последний год многое улучшили, и теперь мы можем использовать его для создания HA-кластеров с несколькими мастер-нодами. Если верить новостям сообщества Kubernetes, в будущем Kubeadm будет рекомендоваться в качестве инструмента для установки Kubernetes.

Документация на Kubeadm предлагает два основных способа реализации кластера, со стековой и внешней etcd-топологией. Я выберу второй путь с внешними etcd нодами по причине отказоустойчивости НА-кластера.

Вот схема из документации Kubeadm, описывающая этот путь:

Я немного ее изменю. Во-первых, буду использовать пару серверов HAProxy в качестве балансировщиков нагрузки с пакетом Heartbeat, которые будут делить виртуальный IP-адрес. Heartbeat и HAProxy используют небольшое количество системных ресурсов, поэтому я размещу их на паре etcd нод, чтобы немного уменьшить число серверов для нашего кластера.

Для этой схемы кластера Kubernetes понадобится восемь нод. Три сервера для внешнего кластера etcd (LB-сервисы также будут использовать пару из их числа), два — для нод плоскости управления (мастер-ноды) и три — для рабочих нод. Это может быть как «голое железо», так и VM-сервер. В данном случае это не принципиально. Можно запросто изменить схему, добавив больше мастер-нод и разместив HAProxy с Heartbeat на отдельных нодах, если свободных серверов много. Хотя моего варианта для первой реализации кластера HA хватит за глаза.

А хотите — добавьте небольшой сервер с установленной утилитой kubectl для управления этим кластером или используйте для этого собственный рабочий стол Linux.

Схема для этого примера будет выглядеть примерно так:


2. Требования

Понадобятся две мастер-ноды Kubernetes с минимальными рекомендованными системными требованиями: 2 ЦП и 2 ГБ ОЗУ в соответствии с документацией kubeadm. Для рабочих нод рекомендую использовать более мощные серверы, поскольку на них будем запускать все наши сервисы приложений. А для Etcd + LB мы также можем взять серверы с двумя ЦП и минимум 2 ГБ ОЗУ.

Выберите сеть общего пользования или частную сеть для этого кластера; IP-адреса значения не имеют; важно, чтобы все серверы были доступны друг для друга и, конечно, для вас. Позже внутри кластера Kubernetes мы настроим оверлейную сеть.

Минимальные требования для этого примера:


  • 2 сервера с 2 процессорами и 2 ГБ оперативной памяти для мастер-нод
  • 3 сервера с 4 процессорами и 4-8 ГБ оперативной памяти для рабочих нод
  • 3 сервера с 2 процессорами и 2 ГБ оперативной памяти для Etcd и HAProxy
  • 192.168.0.0/24  — подсеть.

192.168.0.1 — виртуальный IP-адрес HAProxy, 192.168.0.2 — 4 основных IP-адреса нод Etcd и HAProxy, 192.168.0.5 — 6 основных IP-адресов мастер-нод Kubernetes, 192.168.0.7 — 9 основных IP-адресов рабочих нод Kubernetes.

База Debian 9 установлена на всех серверах.


Также помните, что системные требования зависят от того, насколько большой и мощный кластер нужен. Дополнительную информацию можно получить в документации по Kubernetes.

3. Настройка HAProxy и Heartbeat.

У нас больше одной мастер-ноды Kubernetes, и поэтому необходимо настроить перед ними балансировщик нагрузки HAProxy — распределять трафик. Это будет пара серверов HAProxy с одним общим виртуальным IP-адресом. Отказоустойчивость обеспечивается при помощи пакета Heartbeat. Для развертывания мы воспользуемся первыми двумя etcd серверами.

Установим и настроим HAProxy с Heartbeat на первом и втором etcd -серверах (192.168.0.2–3 в этом примере):

etcd1# apt-get update && apt-get upgrade && apt-get install -y haproxy 
etcd2# apt-get update && apt-get upgrade && apt-get install -y haproxy

Сохраните исходную конфигурацию и создайте новую:

etcd1# mv /etc/haproxy/haproxy.cfg{,.back}
etcd1# vi /etc/haproxy/haproxy.cfg
etcd2# mv /etc/haproxy/haproxy.cfg{,.back}
etcd2# vi /etc/haproxy/haproxy.cfg

Добавьте эти параметры конфигурации для обоих HAProxy:

global
    user haproxy
    group haproxy
defaults
    mode http
    log global
    retries 2
    timeout connect 3000ms
    timeout server 5000ms
    timeout client 5000ms
frontend kubernetes
    bind 192.168.0.1:6443
    option tcplog
    mode tcp
    default_backend kubernetes-master-nodes
backend kubernetes-master-nodes
    mode tcp
    balance roundrobin
    option tcp-check
    server k8s-master-0 192.168.0.5:6443 check fall 3 rise 2
    server k8s-master-1 192.168.0.6:6443 check fall 3 rise 2

Как видите, обе службы HAProxy делят IP-адрес — 192.168.0.1. Этот виртуальный IP-адрес будет перемещаться между серверами, поэтому чутка схитрим и включим параметр net.ipv4.ip_nonlocal_bind, чтобы разрешить привязку системных служб к нелокальному IP-адресу.

Добавьте эту возможность в файл /etc/sysctl.conf:

etcd1# vi /etc/sysctl.conf
net.ipv4.ip_nonlocal_bind=1
etcd2# vi /etc/sysctl.conf
net.ipv4.ip_nonlocal_bind=1

Запустите на обоих серверах:

sysctl -p

Также запустите HAProxy на обоих серверах:

etcd1# systemctl start haproxy
etcd2# systemctl start haproxy

Убедитесь, что HAProxy запущены и прослушиваются по виртуальному IP-адресу на обоих серверах:

etcd1# netstat -ntlp
tcp 0 0 192.168.0.1:6443 0.0.0.0:* LISTEN 2833/haproxy
etcd2# netstat -ntlp
tcp 0 0 192.168.0.1:6443 0.0.0.0:* LISTEN 2833/haproxy

Гуд! Теперь установим Heartbeat и настроим этот виртуальный IP.

etcd1# apt-get -y install heartbeat && systemctl enable heartbeat
etcd2# apt-get -y install heartbeat && systemctl enable heartbeat

Пришло время создать для него несколько файлов конфигурации: для первого и второго серверов они будут, в основном, одинаковыми.

Сначала создайте файл /etc/ha.d/authkeys, в этом файле Heartbeat хранит данные для взаимной аутентификации. Файл должен быть одинаковым на обоих серверах:

# echo -n securepass | md5sum
bb77d0d3b3f239fa5db73bdf27b8d29a
etcd1# vi /etc/ha.d/authkeys
auth 1
1 md5 bb77d0d3b3f239fa5db73bdf27b8d29a
etcd2# vi /etc/ha.d/authkeys
auth 1
1 md5 bb77d0d3b3f239fa5db73bdf27b8d29a

Этот файл должен быть доступен только руту:

etcd1# chmod 600 /etc/ha.d/authkeys
etcd2# chmod 600 /etc/ha.d/authkeys

Теперь создадим основной файл конфигурации для Heartbeat на обоих серверах: для каждого сервера он будет немного отличаться.

Создайте /etc/ha.d/ha.cf:

etcd1

etcd1# vi /etc/ha.d/ha.cf
#       keepalive: how many seconds between heartbeats
#
keepalive 2
#
#       deadtime: seconds-to-declare-host-dead
#
deadtime 10
#
#       What UDP port to use for udp or ppp-udp communication?
#
udpport        694
bcast  ens18
mcast ens18 225.0.0.1 694 1 0
ucast ens18 192.168.0.3
#       What interfaces to heartbeat over?
udp     ens18
#
#       Facility to use for syslog()/logger (alternative to log/debugfile)
#
logfacility     local0
#
#       Tell what machines are in the cluster
#       node    nodename ...    -- must match uname -n
node    etcd1_hostname
node    etcd2_hostname

etcd2

etcd2# vi /etc/ha.d/ha.cf
#       keepalive: how many seconds between heartbeats
#
keepalive 2
#
#       deadtime: seconds-to-declare-host-dead
#
deadtime 10
#
#       What UDP port to use for udp or ppp-udp communication?
#
udpport        694
bcast  ens18
mcast ens18 225.0.0.1 694 1 0
ucast ens18 192.168.0.2
#       What interfaces to heartbeat over?
udp     ens18
#
#       Facility to use for syslog()/logger (alternative to vlog/debugfile)
#
logfacility     local0
#
#       Tell what machines are in the cluster
#       node    nodename ...    -- must match uname -n
node    etcd1_hostname
node    etcd2_hostname

Параметры «ноды» для этой конфигурации получите, запустив uname -n на обоих Etcd серверах. Также используйте имя вашей сетевой карты вместо ens18.

Наконец, нужно создать на этих серверах файл /etc/ha.d/haresources. Для обоих серверов файл должен быть одинаковым. В этом файле мы задаем наш общий IP-адрес и определяем, какая нода является главной по умолчанию:

etcd1# vi /etc/ha.d/haresources
etcd1_hostname 192.168.0.1
etcd2# vi /etc/ha.d/haresources
etcd1_hostname 192.168.0.1

Когда все готово, запускаем службы Heartbeat на обоих серверах и проверяем, что на ноде etcd1 мы получили этот заявленный виртуальный IP:

etcd1# systemctl restart heartbeat
etcd2# systemctl restart heartbeat
etcd1# ip a
ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.2/24 brd 192.168.0.255 scope global ens18
       valid_lft forever preferred_lft forever
    inet 192.168.0.1/24 brd 192.168.0.255 scope global secondary

Вы можете проверить, что HAProxy работает нормально, запустив nc по адресу 192.168.0.1 6443. Должно быть, у вас превышено время ожидания, поскольку Kubernetes API пока не прослушивается на серверной части. Но это означает, что HAProxy и Heartbeat настроены правильно.

# nc -v 192.168.0.1 6443 
Connection to 93.158.95.90 6443 port [tcp/*] succeeded!

4. Подготовка нод для Kubernetes

Следующим шагом подготовим все ноды Kubernetes. Нужно установить Docker с некоторыми дополнительными пакетами, добавить репозиторий Kubernetes и установить из него пакеты kubelet, kubeadm, kubectl. Эта настройка одинакова для всех нод Kubernetes (мастер-, рабочих и проч.)


Основное преимущество Kubeadm в том, что дополнительного ПО много не надо. Установили kubeadm на всех хостах — и пользуйтесь; хоть сертификаты CA генерируйте.

Установка Docker на всех нодах:

Update the apt package index
# apt-get update
Install packages to allow apt to use a repository over HTTPS 
# apt-get -y install \
  apt-transport-https \
  ca-certificates \
  curl \
  gnupg2 \
  software-properties-common
Add Docker’s official GPG key
# curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
Add docker apt repository
# apt-add-repository \
  "deb [arch=amd64] https://download.docker.com/linux/debian \
  $(lsb_release -cs) \
  stable"
Install docker-ce.
# apt-get update && apt-get -y install docker-ce
Check docker version
# docker -v
Docker version 18.09.0, build 4d60db4

После этого установите на всех нодах пакеты Kubernetes:


  • kubeadm: команда для загрузки кластера.
  • kubelet: компонент, который запускается на всех компьютерах в кластере и выполняет действия вроде как запуска подов и контейнеров.
  • kubectl: командная строка util для связи с кластером.
  • kubectl — по желанию; я часто устанавливаю его на всех нодах, чтобы запускать кое-какие команды Kubernetes для отладки.
# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
Add the Google repository
# cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
Update and install packages
# apt-get update && apt-get install -y kubelet kubeadm kubectl
Hold back packages
# apt-mark hold kubelet kubeadm kubectl
Check kubeadm version
# kubeadm version 
kubeadm version: &version.Info{Major:"1", Minor:"13", GitVersion:"v1.13.1", GitCommit:"eec55b9dsfdfgdfgfgdfgdfgdf365bdd920", GitTreeState:"clean", BuildDate:"2018-12-13T10:36:44Z", GoVersion:"go1.11.2", Compiler:"gc", Platform:"linux/amd64"}

Установив kubeadm и остальные пакеты, не забудьте отключить swap.

# swapoff -a
# sed -i '/ swap / s/^/#/' /etc/fstab

Повторите установку на остальных нодах. Пакеты ПО одинаковыми для всех нод кластера, и только следующая конфигурация определит роли, которые они получат позже.


5. Настройка кластера HA Etcd

Итак, завершив приготовления, настроим кластер Kubernetes. Первым кирпичиком будет кластер HA Etcd, который также настраивается с помощью инструмента kubeadm.


Прежде чем начнем, убедитесь, что все etcd ноды сообщаются через порты 2379 и 2380. Кроме того, между ними нужно настроить ssh-доступ — чтобы использовать scp.

Начнем с первой etcd ноды, а затем просто скопируем все необходимые сертификаты и файлы конфигурации на остальные серверы.

На всех etcd нодах нужно добавить новый файл конфигурации systemd для юнита kubelet с более высоким приоритетом:

etcd-nodes# cat << EOF > /etc/systemd/system/kubelet.service.d/20-etcd-service-manager.conf
[Service]
ExecStart=
ExecStart=/usr/bin/kubelet --address=127.0.0.1 --pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true
Restart=always
EOF

etcd-nodes# systemctl daemon-reload
etcd-nodes# systemctl restart kubelet

Затем перейдем по ssh к первой etcd ноде — ее будем использовать для генерации всех необходимых конфигураций kubeadm для каждой etcd ноды, а затем их скопируем.

# Export all our etcd nodes IP's as variables 
etcd1# export HOST0=192.168.0.2
etcd1# export HOST1=192.168.0.3
etcd1# export HOST2=192.168.0.4

# Create temp directories to store files for all nodes
etcd1# mkdir -p /tmp/${HOST0}/ /tmp/${HOST1}/ /tmp/${HOST2}/
etcd1# ETCDHOSTS=(${HOST0} ${HOST1} ${HOST2})
etcd1# NAMES=("infra0" "infra1" "infra2")

etcd1# for i in "${!ETCDHOSTS[@]}"; do
HOST=${ETCDHOSTS[$i]}
NAME=${NAMES[$i]}
cat << EOF > /tmp/${HOST}/kubeadmcfg.yaml
apiVersion: "kubeadm.k8s.io/v1beta1"
kind: ClusterConfiguration
etcd:
    local:
        serverCertSANs:
        - "${HOST}"
        peerCertSANs:
        - "${HOST}"
        extraArgs:
            initial-cluster: ${NAMES[0]}=https://${ETCDHOSTS[0]}:2380,${NAMES[1]}=https://${ETCDHOSTS[1]}:2380,${NAMES[2]}=https://${ETCDHOSTS[2]}:2380
            initial-cluster-state: new
            name: ${NAME}
            listen-peer-urls: https://${HOST}:2380
            listen-client-urls: https://${HOST}:2379
            advertise-client-urls: https://${HOST}:2379
            initial-advertise-peer-urls: https://${HOST}:2380
EOF
done

Теперь создадим основной центр сертификации, используя kubeadm

etcd1# kubeadm init phase certs etcd-ca

Эта команда создаст два файла ca.crt & ca.key в директории /etc/kubernetes/pki/etcd/.

etcd1# ls /etc/kubernetes/pki/etcd/ 
ca.crt  ca.key

Теперь сгенерируем сертификаты для всех etcd нод:

### Create certificates for the etcd3 node 
etcd1# kubeadm init phase certs etcd-server --config=/tmp/${HOST2}/kubeadmcfg.yaml
etcd1# kubeadm init phase certs etcd-peer --config=/tmp/${HOST2}/kubeadmcfg.yaml
etcd1# kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST2}/kubeadmcfg.yaml
etcd1# kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST2}/kubeadmcfg.yaml
etcd1# cp -R /etc/kubernetes/pki /tmp/${HOST2}/
### cleanup non-reusable certificates
etcd1# find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete
### Create certificates for the etcd2 node
etcd1# kubeadm init phase certs etcd-server --config=/tmp/${HOST1}/kubeadmcfg.yaml
etcd1# kubeadm init phase certs etcd-peer --config=/tmp/${HOST1}/kubeadmcfg.yaml
etcd1# kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST1}/kubeadmcfg.yaml
etcd1# kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST1}/kubeadmcfg.yaml
etcd1# cp -R /etc/kubernetes/pki /tmp/${HOST1}/
### cleanup non-reusable certificates again
etcd1# find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete
### Create certificates for the this local node
etcd1# kubeadm init phase certs etcd-server --config=/tmp/${HOST0}/kubeadmcfg.yaml
etcd1 #kubeadm init phase certs etcd-peer --config=/tmp/${HOST0}/kubeadmcfg.yaml
etcd1# kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST0}/kubeadmcfg.yaml
etcd1# kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST0}/kubeadmcfg.yaml
# No need to move the certs because they are for this node
# clean up certs that should not be copied off this host
etcd1# find /tmp/${HOST2} -name ca.key -type f -delete
etcd1# find /tmp/${HOST1} -name ca.key -type f -delete

Затем скопируем сертификаты и конфигурации kubeadm на ноды etcd2 и etcd3.


Сначала сгенерируйте пару ssh-ключей на etcd1 и добавьте открытую часть к нодам etcd2 и 3. В этом примере все команды выполняются от имени пользователя, владеющего всеми правами в системе.
etcd1# scp -r /tmp/${HOST1}/* ${HOST1}:
etcd1# scp -r /tmp/${HOST2}/* ${HOST2}:
### login to the etcd2 or run this command remotely by ssh
etcd2# cd /root
etcd2# mv pki /etc/kubernetes/
### login to the etcd3 or run this command remotely by ssh
etcd3# cd /root
etcd3# mv pki /etc/kubernetes/

Прежде чем запустить кластер etcd, убедитесь, что файлы существуют на всех нодах:

Список необходимых файлов на etcd1:

/tmp/192.168.0.2
└── kubeadmcfg.yaml
---
/etc/kubernetes/pki
├── apiserver-etcd-client.crt
├── apiserver-etcd-client.key
└── etcd
    ├── ca.crt
    ├── ca.key
    ├── healthcheck-client.crt
    ├── healthcheck-client.key
    ├── peer.crt
    ├── peer.key
    ├── server.crt
    └── server.key

Для ноды etcd2 это:

/root
└── kubeadmcfg.yaml
---
/etc/kubernetes/pki
├── apiserver-etcd-client.crt
├── apiserver-etcd-client.key
└── etcd
    ├── ca.crt
    ├── healthcheck-client.crt
    ├── healthcheck-client.key
    ├── peer.crt
    ├── peer.key
    ├── server.crt
    └── server.key

И последняя нода etcd3:

/root
└── kubeadmcfg.yaml
---
/etc/kubernetes/pki
├── apiserver-etcd-client.crt
├── apiserver-etcd-client.key
└── etcd
    ├── ca.crt
    ├── healthcheck-client.crt
    ├── healthcheck-client.key
    ├── peer.crt
    ├── peer.key
    ├── server.crt
    └── server.key

Когда все сертификаты и конфигурации на месте, создаем манифесты. На каждой ноде выполните команду kubeadm — чтобы сгенерировать статический манифест для кластера etcd:

etcd1# kubeadm init phase etcd local --config=/tmp/192.168.0.2/kubeadmcfg.yaml
etcd1# kubeadm init phase etcd local --config=/root/kubeadmcfg.yaml
etcd1# kubeadm init phase etcd local --config=/root/kubeadmcfg.yaml

Теперь кластер etcd — по идее — настроен и исправен. Проверьте, выполнив следующую команду на ноде etcd1:

etcd1# docker run --rm -it \
--net host \
-v /etc/kubernetes:/etc/kubernetes quay.io/coreos/etcd:v3.2.24 etcdctl \
--cert-file /etc/kubernetes/pki/etcd/peer.crt \
--key-file /etc/kubernetes/pki/etcd/peer.key \
--ca-file /etc/kubernetes/pki/etcd/ca.crt \
--endpoints https://192.168.0.2:2379 cluster-health
### status output
member 37245675bd09ddf3 is healthy: got healthy result from https://192.168.0.3:2379 
member 532d748291f0be51 is healthy: got healthy result from https://192.168.0.4:2379 
member 59c53f494c20e8eb is healthy: got healthy result from https://192.168.0.2:2379 
cluster is healthy

Кластер etcd поднялся, так что двигаемся дальше.


6. Настройка мастер- и рабочих нод

Настроим мастер-ноды нашего кластера — скопируйте эти файлы с первой ноды etcd на первую мастер-ноду:

etcd1# scp /etc/kubernetes/pki/etcd/ca.crt 192.168.0.5:
etcd1# scp /etc/kubernetes/pki/apiserver-etcd-client.crt 192.168.0.5:
etcd1# scp /etc/kubernetes/pki/apiserver-etcd-client.key 192.168.0.5:

Затем перейдите по ssh к мастер-ноде master1 и создайте файл kubeadm-config.yaml со следующим содержанием:

master1# cd /root && vi kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: stable
apiServer:
  certSANs:
  - "192.168.0.1"
controlPlaneEndpoint: "192.168.0.1:6443"
etcd:
    external:
        endpoints:
        - https://192.168.0.2:2379
        - https://192.168.0.3:2379
        - https://192.168.0.4:2379
        caFile: /etc/kubernetes/pki/etcd/ca.crt
        certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
        keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key

Переместите ранее скопированные сертификаты и ключ в соответствующую директорию на ноде master1, как в описании настройки.

master1# mkdir -p /etc/kubernetes/pki/etcd/ 
master1# cp /root/ca.crt /etc/kubernetes/pki/etcd/
master1# cp /root/apiserver-etcd-client.crt /etc/kubernetes/pki/
master1# cp /root/apiserver-etcd-client.key /etc/kubernetes/pki/

Чтобы создать первую мастер-ноду, выполните:

master1# kubeadm init --config kubeadm-config.yaml

Если все предыдущие шаги выполнены правильно, вы увидите следующее:

You can now join any number of machines by running the following on each node
as root:
kubeadm join 192.168.0.1:6443 --token aasuvd.kw8m18m5fy2ot387 --discovery-token-ca-cert-hash sha256:dcbaeed8d1478291add0294553b6b90b453780e546d06162c71d515b494177a6

Скопируйте этот вывод инициализации kubeadm в любой текстовый файл, мы будем использовать этот токен в будущем, когда присоединим к нашему кластеру вторую мастер- и рабочую ноды.

Я уже говорил, что кластер Kubernetes будет использовать некую оверлейную сеть для подов и других сервисов, поэтому на этом этапе нужно установить какой-либо плагин CNI. Я рекомендую плагин Weave CNI. Опыт показал: он полезней и менее проблемный, — но вы можете выбрать другой, например, Calico.

Установка сетевого плагина Weave на первую мастер-ноду:

master1# kubectl --kubeconfig /etc/kubernetes/admin.conf apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
The connection to the server localhost:8080 was refused - did you specify the right host or port?
serviceaccount/weave-net created
clusterrole.rbac.authorization.k8s.io/weave-net created
clusterrolebinding.rbac.authorization.k8s.io/weave-net created
role.rbac.authorization.k8s.io/weave-net created
rolebinding.rbac.authorization.k8s.io/weave-net created
daemonset.extensions/weave-net created

Немного подождите, а затем введите следующую команду для проверки запуска подов компонентов:

master1# kubectl --kubeconfig /etc/kubernetes/admin.conf get pod -n kube-system -w
NAME                             READY   STATUS   RESTARTS AGE 
coredns-86c58d9df4-d7qfw         1/1     Running  0        6m25s 
coredns-86c58d9df4-xj98p         1/1     Running  0        6m25s 
kube-apiserver-master1           1/1     Running  0        5m22s 
kube-controller-manager-master1  1/1     Running  0        5m41s 
kube-proxy-8ncqw                 1/1     Running  0        6m25s 
kube-scheduler-master1           1/1     Running  0        5m25s 
weave-net-lvwrp                  2/2     Running  0        78s

  • Рекомендуется присоединять новые ноды плоскости управления только после инициализации первой ноды.

Чтобы проверить состояние кластера, выполните:

master1# kubectl --kubeconfig /etc/kubernetes/admin.conf get nodes
NAME      STATUS   ROLES    AGE   VERSION 
master1   Ready    master   11m   v1.13.1

Прекрасно! Первая мастер-нода поднялась. Теперь она готова, и мы закончим создавать кластер Kubernetes — добавим вторую мастер-ноду и рабочие ноды.
Чтобы добавить вторую мастер-ноду, создайте ssh-ключ на ноде master1 и добавьте открытую часть в ноду master2. Выполните тестовый вход в систему, а затем скопируйте кое-какие файлы с первой мастер-ноды на вторую:

master1# scp /etc/kubernetes/pki/ca.crt 192.168.0.6:
master1# scp /etc/kubernetes/pki/ca.key 192.168.0.6:
master1# scp /etc/kubernetes/pki/sa.key 192.168.0.6:
master1# scp /etc/kubernetes/pki/sa.pub 192.168.0.6:
master1# scp /etc/kubernetes/pki/front-proxy-ca.crt @192.168.0.6:
master1# scp /etc/kubernetes/pki/front-proxy-ca.key @192.168.0.6:
master1# scp /etc/kubernetes/pki/apiserver-etcd-client.crt @192.168.0.6:
master1# scp /etc/kubernetes/pki/apiserver-etcd-client.key @192.168.0.6:
master1# scp /etc/kubernetes/pki/etcd/ca.crt 192.168.0.6:etcd-ca.crt
master1# scp /etc/kubernetes/admin.conf 192.168.0.6:
### Check that files was copied well
master2# ls /root
admin.conf  ca.crt  ca.key  etcd-ca.crt  front-proxy-ca.crt  front-proxy-ca.key  sa.key  sa.pub

На второй мастер-ноде переместите ранее скопированные сертификаты и ключи в соответствующие директории:

master2#
mkdir -p /etc/kubernetes/pki/etcd
mv /root/ca.crt /etc/kubernetes/pki/
mv /root/ca.key /etc/kubernetes/pki/
mv /root/sa.pub /etc/kubernetes/pki/
mv /root/sa.key /etc/kubernetes/pki/
mv /root/apiserver-etcd-client.crt /etc/kubernetes/pki/ 
mv /root/apiserver-etcd-client.key /etc/kubernetes/pki/
mv /root/front-proxy-ca.crt /etc/kubernetes/pki/
mv /root/front-proxy-ca.key /etc/kubernetes/pki/
mv /root/etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt
mv /root/admin.conf /etc/kubernetes/admin.conf

Присоединим вторую мастер-ноду к кластеру. Для этого понадобится вывод команды соединения, который был ранее передан нам kubeadm init на первой ноде.

Запустите мастер-ноду master2:

master2# kubeadm join 192.168.0.1:6443 --token aasuvd.kw8m18m5fy2ot387 --discovery-token-ca-cert-hash sha256:dcbaeed8d1478291add0294553b6b90b453780e546d06162c71d515b494177a6 --experimental-control-plane

  • Нужно добавить флажок --experimental-control-plane. Он автоматизирует присоединение данных мастер-нод к кластеру. Без этого флажка просто добавится обычная рабочая нода.

Подождите немного, пока нода не присоединится к кластеру, и проверьте новое состояние кластера:

master1# kubectl --kubeconfig /etc/kubernetes/admin.conf get nodes
NAME      STATUS   ROLES    AGE   VERSION 
master1   Ready    master   32m   v1.13.1 
master2   Ready    master   46s   v1.13.1

Также убедитесь, что все поды со всех мастер-нод запущены нормально:

master1# kubectl — kubeconfig /etc/kubernetes/admin.conf get pod -n kube-system -w

NAME                            READY  STATUS   RESTARTS  AGE 
coredns-86c58d9df4-d7qfw        1/1    Running  0         46m 
coredns-86c58d9df4-xj98p        1/1    Running  0         46m 
kube-apiserver-master1          1/1    Running  0         45m 
kube-apiserver-master2          1/1    Running  0         15m 
kube-controller-manager-master1 1/1    Running  0         45m 
kube-controller-manager-master2 1/1    Running  0         15m 
kube-proxy-8ncqw                1/1    Running  0         46m 
kube-proxy-px5dt                1/1    Running  0         15m 
kube-scheduler-master1          1/1    Running  0         45m 
kube-scheduler-master2          1/1    Running  0         15m 
weave-net-ksvxz                 2/2    Running  1         15m 
weave-net-lvwrp                 2/2    Running  0         41m

Замечательно! Мы почти закончили конфигурацию кластера Kubernetes. И последнее, что нужно сделать, это добавить три рабочих ноды, которые мы подготовили ранее.

Войдите в рабочие ноды и выполните команду kubeadm join без флажка --experimental-control-plane.

worker1-3# kubeadm join 192.168.0.1:6443 --token aasuvd.kw8m18m5fy2ot387 --discovery-token-ca-cert-hash sha256:dcbaeed8d1478291add0294553b6b90b453780e546d06162c71d515b494177a6

Еще раз проверим состояние кластера:

master1# kubectl --kubeconfig /etc/kubernetes/admin.conf get nodes
NAME           STATUS   ROLES    AGE     VERSION 
master1   Ready    master   1h30m   v1.13.1 
master2   Ready    master   1h59m   v1.13.1 
worker1   Ready    <none>   1h8m    v1.13.1 
worker2   Ready    <none>   1h8m    v1.13.1 
worker3   Ready    <none>   1h7m    v1.13.1

Как видите, у нас есть полностью настроенный HA-кластер Kubernetes с двумя мастер- и тремя рабочими нодами. Он построен на основе кластера HA etcd с отказоустойчивым балансировщиком нагрузки перед мастер-нодами. Как по мне, звучит неплохо.


7. Настройка удаленного управления кластером

Еще одно действие, которое осталось рассмотреть в этой — первой — части статьи, это настройка удаленной утилиты kubectl для управления кластером. Ранее мы запускали все команды с мастер-ноды master1, но это подходит только для первого раза — при настройке кластера. Было бы неплохо настроить внешнюю ноду управления. Для этого можно использовать ноутбук или другой сервер.

Войдите на этот сервер и запустите:

Add the Google repository key
control# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
Add the Google repository
control# cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
Update and install kubectl
control# apt-get update && apt-get install -y kubectl
In your user home dir create
control# mkdir ~/.kube
Take the Kubernetes admin.conf from the master1 node
control# scp 192.168.0.5:/etc/kubernetes/admin.conf ~/.kube/config
Check that we can send commands to our cluster
control# kubectl get nodes
NAME           STATUS   ROLES    AGE     VERSION 
master1        Ready    master   6h58m   v1.13.1 
master2        Ready    master   6h27m   v1.13.1 
worker1        Ready    <none>   5h36m   v1.13.1 
worker2        Ready    <none>   5h36m   v1.13.1 
worker3        Ready    <none>   5h36m   v1.13.1

Хорошо, а теперь запустим тестовый под в нашем кластере и проверим, как он работает.

control# kubectl create deployment nginx --image=nginx 
deployment.apps/nginx created
control# kubectl get pods 
NAME                   READY   STATUS    RESTARTS   AGE 
nginx-5c7588df-6pvgr   1/1     Running   0          52s

Поздравляю! Вы только что задеплоили Kubernetes. И это означает, что ваш новый HA-кластер готов. На самом деле, процесс настройки кластера Kubernetes с использованием kubeadm довольно простой и быстрый.

В следующей части статьи добавим внутреннее хранилище, настроив GlusterFS на всех рабочих нодах, настроим внутренний балансировщик нагрузки для нашего кластера Kubernetes, а также запустим определенные стресс-тесты, отключив кое-какие ноды, и проверим кластер на стабильность.


Послесловие

Да, работая по данному примеру, вы столкнетесь с рядом проблем. Волноваться не надо: чтобы отменить изменения и вернуть ноды в исходное состояние, просто запустите kubeadm reset — изменения, которые kubeadm внес ранее, сбросятся, и можно настраиваться заново. Также не забудьте проверить состояние Docker-контейнеров на нодах кластера — убедитесь, что все они запускаются и работают без ошибок. Для дополнительной информации о поврежденных контейнерах используйте команду docker logs containerid.

На сегодня все. Удачи!

Let's block ads! (Why?)